strong_presenter 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -2
- data/.rspec +2 -0
- data/.travis.yml +18 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +31 -0
- data/Gemfile +22 -2
- data/Guardfile +26 -0
- data/README.md +210 -52
- data/Rakefile +77 -1
- data/gemfiles/3.0.gemfile +2 -0
- data/gemfiles/3.1.gemfile +2 -0
- data/gemfiles/3.2.gemfile +2 -0
- data/gemfiles/4.0.gemfile +2 -0
- data/gemfiles/4.1.gemfile +2 -0
- data/lib/generators/controller_override.rb +15 -0
- data/lib/generators/mini_test/presenter_generator.rb +20 -0
- data/lib/generators/mini_test/templates/presenter_spec.rb +4 -0
- data/lib/generators/mini_test/templates/presenter_test.rb +4 -0
- data/lib/generators/rails/presenter_generator.rb +36 -0
- data/lib/generators/rails/templates/presenter.rb +19 -0
- data/lib/generators/rspec/presenter_generator.rb +9 -0
- data/lib/generators/rspec/templates/presenter_spec.rb +4 -0
- data/lib/generators/test_unit/presenter_generator.rb +9 -0
- data/lib/generators/test_unit/templates/presenter_test.rb +4 -0
- data/lib/strong_presenter/associable.rb +78 -0
- data/lib/strong_presenter/collection_presenter.rb +90 -0
- data/lib/strong_presenter/controller_additions.rb +50 -0
- data/lib/strong_presenter/delegation.rb +18 -0
- data/lib/strong_presenter/factory.rb +74 -0
- data/lib/strong_presenter/helper_proxy.rb +29 -11
- data/lib/strong_presenter/inferrer.rb +54 -0
- data/lib/strong_presenter/permissible.rb +73 -0
- data/lib/strong_presenter/permissions.rb +138 -0
- data/lib/strong_presenter/presenter.rb +191 -0
- data/lib/strong_presenter/presenter_association.rb +29 -0
- data/lib/strong_presenter/presenter_helper_constructor.rb +60 -0
- data/lib/strong_presenter/railtie.rb +27 -3
- data/lib/strong_presenter/tasks/test.rake +22 -0
- data/lib/strong_presenter/test/devise_helper.rb +30 -0
- data/lib/strong_presenter/test/minitest_integration.rb +6 -0
- data/lib/strong_presenter/test/rspec_integration.rb +16 -0
- data/lib/strong_presenter/test_case.rb +53 -0
- data/lib/strong_presenter/version.rb +1 -1
- data/lib/strong_presenter/view_context/build_strategy.rb +48 -0
- data/lib/strong_presenter/view_context.rb +84 -0
- data/lib/strong_presenter/view_helpers.rb +39 -0
- data/lib/strong_presenter.rb +64 -2
- data/spec/dummy/.rspec +2 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/dummy/app/controllers/localized_urls.rb +5 -0
- data/spec/dummy/app/controllers/posts_controller.rb +25 -0
- data/spec/dummy/app/helpers/application_helper.rb +5 -0
- data/spec/dummy/app/mailers/application_mailer.rb +3 -0
- data/spec/dummy/app/mailers/post_mailer.rb +19 -0
- data/spec/dummy/app/models/admin.rb +5 -0
- data/spec/dummy/app/models/post.rb +3 -0
- data/spec/dummy/app/models/user.rb +5 -0
- data/spec/dummy/app/presenters/post_presenter.rb +69 -0
- data/spec/dummy/app/presenters/special_post_presenter.rb +5 -0
- data/spec/dummy/app/presenters/special_posts_presenter.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +11 -0
- data/spec/dummy/app/views/post_mailer/presented_email.html.erb +1 -0
- data/spec/dummy/app/views/posts/_post.html.erb +50 -0
- data/spec/dummy/app/views/posts/show.html.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/config/application.rb +70 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +33 -0
- data/spec/dummy/config/environments/production.rb +57 -0
- data/spec/dummy/config/environments/test.rb +31 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +9 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20121019115657_create_posts.rb +8 -0
- data/spec/dummy/db/schema.rb +21 -0
- data/spec/dummy/db/seeds.rb +2 -0
- data/spec/dummy/fast_spec/post_presenter_spec.rb +37 -0
- data/spec/dummy/lib/tasks/test.rake +16 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/fast_spec_helper.rb +13 -0
- data/spec/dummy/spec/mailers/post_mailer_spec.rb +33 -0
- data/spec/dummy/spec/models/post_spec.rb +4 -0
- data/spec/dummy/spec/presenters/active_model_serializers_spec.rb +11 -0
- data/spec/dummy/spec/presenters/devise_spec.rb +64 -0
- data/spec/dummy/spec/presenters/helpers_spec.rb +21 -0
- data/spec/dummy/spec/presenters/post_presenter_spec.rb +66 -0
- data/spec/dummy/spec/presenters/spec_type_spec.rb +7 -0
- data/spec/dummy/spec/presenters/special_post_presenter_spec.rb +11 -0
- data/spec/dummy/spec/presenters/view_context_spec.rb +22 -0
- data/spec/dummy/spec/spec_helper.rb +19 -0
- data/spec/dummy/test/minitest_helper.rb +2 -0
- data/spec/dummy/test/presenters/minitest/devise_test.rb +64 -0
- data/spec/dummy/test/presenters/minitest/helpers_test.rb +21 -0
- data/spec/dummy/test/presenters/minitest/spec_type_test.rb +52 -0
- data/spec/dummy/test/presenters/minitest/view_context_test.rb +24 -0
- data/spec/dummy/test/presenters/test_unit/devise_test.rb +64 -0
- data/spec/dummy/test/presenters/test_unit/helpers_test.rb +21 -0
- data/spec/dummy/test/presenters/test_unit/view_context_test.rb +24 -0
- data/spec/dummy/test/test_helper.rb +13 -0
- data/spec/generators/presenters/presenter_generator_spec.rb +131 -0
- data/spec/generators/simplecov_spec.rb +5 -0
- data/spec/integration/integration_spec.rb +81 -0
- data/spec/integration/simplecov_spec.rb +4 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/strong_presenter/associable_spec.rb +122 -0
- data/spec/strong_presenter/collection_presenter_spec.rb +34 -0
- data/spec/strong_presenter/delegation_spec.rb +20 -0
- data/spec/strong_presenter/permissible_spec.rb +24 -0
- data/spec/strong_presenter/permissions_spec.rb +188 -0
- data/spec/strong_presenter/presenter_spec.rb +43 -0
- data/spec/strong_presenter/simplecov_spec.rb +4 -0
- data/spec/support/dummy_app.rb +85 -0
- data/spec/support/matchers/have_text.rb +50 -0
- data/spec/support/models.rb +14 -0
- data/spec/support/schema.rb +12 -0
- data/strong_presenter.gemspec +15 -0
- metadata +392 -13
- data/lib/strong_presenter/base.rb +0 -217
@@ -0,0 +1,36 @@
|
|
1
|
+
module Rails
|
2
|
+
module Generators
|
3
|
+
class PresenterGenerator < NamedBase
|
4
|
+
source_root File.expand_path("../templates", __FILE__)
|
5
|
+
check_class_collision suffix: "Presenter"
|
6
|
+
|
7
|
+
class_option :parent, type: :string, desc: "The parent class for the generated presenter"
|
8
|
+
|
9
|
+
def create_presenter_file
|
10
|
+
template 'presenter.rb', File.join('app/presenters', class_path, "#{file_name}_presenter.rb")
|
11
|
+
end
|
12
|
+
|
13
|
+
hook_for :test_framework
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parent_class_name
|
18
|
+
options.fetch("parent") do
|
19
|
+
begin
|
20
|
+
require 'application_presenter'
|
21
|
+
ApplicationPresenter
|
22
|
+
rescue LoadError
|
23
|
+
"StrongPresenter::Presenter"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Rails 3.0.X compatibility, stolen from https://github.com/jnunemaker/mongomapper/pull/385/files#L1R32
|
29
|
+
unless methods.include?(:module_namespacing)
|
30
|
+
def module_namespacing
|
31
|
+
yield if block_given?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<%- module_namespacing do -%>
|
2
|
+
<%- if parent_class_name.present? -%>
|
3
|
+
class <%= class_name %>Presenter < <%= parent_class_name %>
|
4
|
+
<%- else -%>
|
5
|
+
class <%= class_name %>
|
6
|
+
<%- end -%>
|
7
|
+
delegate_all
|
8
|
+
|
9
|
+
# Define presentation-specific methods here. Helpers are accessed through
|
10
|
+
# `helpers` (aka `h`). You can override attributes, for example:
|
11
|
+
#
|
12
|
+
# def created_at
|
13
|
+
# helpers.content_tag :span, class: 'time' do
|
14
|
+
# object.created_at.strftime("%a %m/%d/%y")
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
|
18
|
+
end
|
19
|
+
<% end -%>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module Rspec
|
2
|
+
class PresenterGenerator < ::Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def create_spec_file
|
6
|
+
template 'presenter_spec.rb', File.join('spec/presenters', class_path, "#{singular_name}_presenter_spec.rb")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module TestUnit
|
2
|
+
class PresenterGenerator < ::Rails::Generators::NamedBase
|
3
|
+
source_root File.expand_path('../templates', __FILE__)
|
4
|
+
|
5
|
+
def create_test_file
|
6
|
+
template 'presenter_test.rb', File.join('test/presenters', class_path, "#{singular_name}_presenter_test.rb")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
|
3
|
+
# Methods for defining presenter associations
|
4
|
+
module Associable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Automatically wraps multiple associations.
|
9
|
+
# @param [Symbol] association
|
10
|
+
# name of the association to wrap.
|
11
|
+
# @option options [Class] :with
|
12
|
+
# the presenter to apply to the association.
|
13
|
+
# @option options [Symbol] :scope
|
14
|
+
# a scope to apply when fetching the association.
|
15
|
+
# @yield
|
16
|
+
# block executed when association presenter is initialized, in
|
17
|
+
# the context of the parent presenter instance (instance_exec-ed)
|
18
|
+
# @yieldparam [Presenter] the association presenter
|
19
|
+
# @return [void]
|
20
|
+
def presents_association(association, options = {})
|
21
|
+
options.assert_valid_keys(:with, :scope)
|
22
|
+
options[:with] = Associable.object_association_class(object_class, association) unless options.has_key? :with
|
23
|
+
presenter_associations[association] ||= StrongPresenter::PresenterAssociation.new(association, options) do |presenter|
|
24
|
+
presenter.link_permissions self, association
|
25
|
+
yield presenter if block_given?
|
26
|
+
end
|
27
|
+
define_method(association) do
|
28
|
+
self.class.send(:presenter_associations)[association].wrap(self)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @overload presents_associations(*associations, options = {})
|
33
|
+
# Automatically wraps multiple associations.
|
34
|
+
# @param [Symbols*] associations
|
35
|
+
# names of the associations to wrap.
|
36
|
+
# @option options [Class] :with
|
37
|
+
# the presenter to apply to the association.
|
38
|
+
# @option options [Symbol] :scope
|
39
|
+
# a scope to apply when fetching the association.
|
40
|
+
# @yield
|
41
|
+
# block executed when association presenter is initialized, in
|
42
|
+
# the context of the parent presenter instance (instance_exec-ed)
|
43
|
+
# @yieldparam [Presenter] the association presenter
|
44
|
+
# @return [void]
|
45
|
+
def presents_associations(*associations)
|
46
|
+
options = associations.extract_options!
|
47
|
+
options.assert_valid_keys(:with, :scope)
|
48
|
+
associations.each { |association| presents_association(association, options) {|presenter| yield if block_given?} }
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def presenter_associations
|
53
|
+
@presenter_associations ||= {}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
# infer association class if possible
|
60
|
+
def self.object_association_class(object_class, association)
|
61
|
+
if self.descendant_of(object_class, "ActiveRecord::Reflection")
|
62
|
+
association_class = object_class.reflect_on_association(association).klass
|
63
|
+
else
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
"#{association_class}Presenter".constantize
|
67
|
+
rescue NameError
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.descendant_of(object_class, klass)
|
72
|
+
object_class.ancestors.include? klass.constantize
|
73
|
+
rescue NameError
|
74
|
+
false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
class CollectionPresenter
|
3
|
+
include Enumerable
|
4
|
+
include StrongPresenter::ViewHelpers
|
5
|
+
include StrongPresenter::Permissible
|
6
|
+
include StrongPresenter::Delegation
|
7
|
+
|
8
|
+
array_methods = Array.instance_methods - Object.instance_methods
|
9
|
+
delegate :==, :as_json, *array_methods, to: :collection
|
10
|
+
|
11
|
+
# @param [Enumerable] object
|
12
|
+
# collection to present
|
13
|
+
def initialize(object, options = {})
|
14
|
+
options.assert_valid_keys(:with)
|
15
|
+
@object = object
|
16
|
+
@presenter_class = options[:with]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"#<#{self.class.name} of #{presenter_class || "inferred presenters"} for #{object.inspect}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
# @return [Class] the presenter class used to present each item, as set by
|
25
|
+
# {#initialize}.
|
26
|
+
def presenter_class
|
27
|
+
@presenter_class = @presenter_class.nil? ? self.class.send(:presenter_class) : @presenter_class
|
28
|
+
end
|
29
|
+
|
30
|
+
def item_presenter(item)
|
31
|
+
return presenter_class if presenter_class
|
32
|
+
StrongPresenter::Presenter.inferred_presenter(item)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return the collection being presented.
|
36
|
+
attr_reader :object
|
37
|
+
|
38
|
+
# Wraps the given item.
|
39
|
+
def wrap_item(item)
|
40
|
+
item_presenter(item).new(item).tap do |presenter|
|
41
|
+
presenter.link_permissions self # item's permitted_attributes is linked to collection
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array] the items being presented
|
46
|
+
def collection
|
47
|
+
@collection ||= object.map{|item| wrap_item(item)}
|
48
|
+
end
|
49
|
+
|
50
|
+
class << self
|
51
|
+
# Sets the presenter used to wrap models in the collection
|
52
|
+
def presents_with presenter
|
53
|
+
@presenter_class = presenter
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
def set_item_presenter_collection
|
59
|
+
collection = self
|
60
|
+
presenter_class.instance_exec do
|
61
|
+
unless nil?
|
62
|
+
if !const_defined? :Collection
|
63
|
+
const_set :Collection, collection
|
64
|
+
elsif self::Collection.name.demodulize == "Collection"
|
65
|
+
remove_const :Collection
|
66
|
+
const_set :Collection, collection
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue NameError => error
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def inherited(subclass)
|
75
|
+
subclass.set_item_presenter_collection
|
76
|
+
super
|
77
|
+
end
|
78
|
+
|
79
|
+
def inferred_presenter_class
|
80
|
+
presenter_class = Inferrer.new(name).chomp("Presenter").inferred_class { |name| "#{name.singularize}Presenter" }
|
81
|
+
presenter_class == self ? nil : presenter_class
|
82
|
+
end
|
83
|
+
|
84
|
+
def presenter_class
|
85
|
+
@presenter_class ||= inferred_presenter_class
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
module ControllerAdditions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# @overload presents(*variables, options = {})
|
7
|
+
# Defines a helper method to access instance variables wrapped in presenters.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # app/controllers/articles_controller.rb
|
11
|
+
# class ArticlesController < ApplicationController
|
12
|
+
# presents :article
|
13
|
+
# presents :comments, with: ArticleCommentsPresenter, only: :show
|
14
|
+
# presents :comments, with: CommentsPresenter, only: :index { |presenter| presenter.permit(:author, :text) }
|
15
|
+
#
|
16
|
+
# def show
|
17
|
+
# @article = Article.find(params[:id])
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# # app/views/articles/show.html.erb
|
22
|
+
# <%= article.presented_title %>
|
23
|
+
#
|
24
|
+
# @param [Symbols*] variables
|
25
|
+
# names of the instance variables to present (without the `@`).
|
26
|
+
# @param [Hash] options
|
27
|
+
# @option options [Presenter, CollectionPresenter] :with (nil)
|
28
|
+
# presenter class to use. If nil, it is inferred from the instance
|
29
|
+
# variable.
|
30
|
+
# @option options [Symbols*] :only (nil)
|
31
|
+
# apply presenter only on these controller actions.
|
32
|
+
# @option options [Symbols*] :except (nil)
|
33
|
+
# don't apply presenter on these controller actions.
|
34
|
+
# @yield [Presenter] code to execute when presenter is initialized
|
35
|
+
def presents(*variables, &block)
|
36
|
+
options = variables.extract_options!
|
37
|
+
options.assert_valid_keys(:with, :only, :except)
|
38
|
+
|
39
|
+
constructor = StrongPresenter::PresenterHelperConstructor.new(self, block, options)
|
40
|
+
|
41
|
+
variables.each do |variable|
|
42
|
+
constructor.call(variable)
|
43
|
+
helper_method variable
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
module Delegation
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# @overload delegate(*methods, options = {})
|
7
|
+
# Overrides {http://api.rubyonrails.org/classes/Module.html#method-i-delegate Module.delegate}
|
8
|
+
# to make `:object` the default delegation target.
|
9
|
+
#
|
10
|
+
# @return [void]
|
11
|
+
def delegate(*methods)
|
12
|
+
options = methods.extract_options!
|
13
|
+
super *methods, options.reverse_merge(to: :object)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
class Factory
|
3
|
+
# Creates a presenter factory.
|
4
|
+
#
|
5
|
+
# @option options [Presenter, CollectionPresenter] :with (nil)
|
6
|
+
# presenter class to use. If nil, it is inferred from the object
|
7
|
+
# passed to {#wrap}.
|
8
|
+
def initialize(options = {})
|
9
|
+
options.assert_valid_keys(:with)
|
10
|
+
@presenter_class = options.delete(:with)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Wraps an object with a presenter, inferring whether to create a singular or collection
|
14
|
+
# presenter from the type of object passed.
|
15
|
+
#
|
16
|
+
# @param [Object] object
|
17
|
+
# object to present.
|
18
|
+
# @return [Presenter, CollectionPresenter] the presenter.
|
19
|
+
def wrap(object)
|
20
|
+
return nil if object.nil?
|
21
|
+
Worker.new(presenter_class, object).call { |presenter| yield presenter if block_given? }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :presenter_class
|
27
|
+
|
28
|
+
# @private
|
29
|
+
class Worker
|
30
|
+
def initialize(presenter_class, object)
|
31
|
+
@presenter_class = presenter_class
|
32
|
+
@object = object
|
33
|
+
@presenter_class = presenter_class::Collection if collection? && !presenter_class.nil? && (presenter_class < StrongPresenter::Presenter)
|
34
|
+
end
|
35
|
+
|
36
|
+
def call
|
37
|
+
presenter.new(object) { |presenter| yield presenter if block_given? }
|
38
|
+
end
|
39
|
+
|
40
|
+
def presenter
|
41
|
+
return presenter_class if presenter_class
|
42
|
+
return object_presenter if object_presenter
|
43
|
+
return StrongPresenter::CollectionPresenter if collection?
|
44
|
+
raise StrongPresenter::UninferrablePresenterError.new(object.class)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :presenter_class, :object
|
50
|
+
|
51
|
+
def object_presenter
|
52
|
+
@object_presenter = @object_presenter.nil? ? get_object_presenter : @object_presenter
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_object_presenter
|
56
|
+
StrongPresenter::Presenter.inferred_presenter(object)
|
57
|
+
rescue NameError => error
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def collection?
|
62
|
+
object.is_a?(Enumerable) or is_a?("ActiveRecord::Associations::CollectionProxy") # or any other wrappers
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if object is an instance of klass, false in case klass does not exist.
|
66
|
+
def is_a? klass
|
67
|
+
object.is_a? klass.constantize
|
68
|
+
rescue NameError
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -1,16 +1,34 @@
|
|
1
1
|
module StrongPresenter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
2
|
+
# Provides access to helper methods - both Rails built-in helpers, and those
|
3
|
+
# defined in your application.
|
4
|
+
|
5
|
+
# Copied from Draper::HelperProxy
|
6
|
+
class HelperProxy
|
7
|
+
|
8
|
+
# @overload initialize(view_context)
|
9
|
+
def initialize(view_context)
|
10
|
+
@view_context = view_context
|
11
11
|
end
|
12
|
-
|
13
|
-
|
12
|
+
|
13
|
+
# Sends helper methods to the view context.
|
14
|
+
def method_missing(method, *args, &block)
|
15
|
+
self.class.define_proxy method
|
16
|
+
send(method, *args, &block)
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :capture, to: :view_context
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
attr_reader :view_context
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.define_proxy(name)
|
28
|
+
define_method name do |*args, &block|
|
29
|
+
view_context.send(name, *args, &block)
|
30
|
+
end
|
14
31
|
end
|
32
|
+
|
15
33
|
end
|
16
34
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
# @private
|
3
|
+
# Helper class for inferring class names
|
4
|
+
class Inferrer
|
5
|
+
attr_accessor :input, :suffix
|
6
|
+
|
7
|
+
# Constructs inferer object
|
8
|
+
# @param [String] input
|
9
|
+
# Input name as base to infer from
|
10
|
+
def initialize(input)
|
11
|
+
self.input = input
|
12
|
+
end
|
13
|
+
|
14
|
+
# Sets input suffix
|
15
|
+
# @param [String] suffix
|
16
|
+
# Suffix which must be present in input to chomp/remove
|
17
|
+
# @return [self] chainable
|
18
|
+
def chomp(suffix)
|
19
|
+
self.suffix = suffix
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Extracts name by removing suffix
|
24
|
+
# @return [String]
|
25
|
+
def extract_name
|
26
|
+
raise UnextractableError if input.nil? || input.demodulize !~ /.+#{suffix}$/
|
27
|
+
input.chomp(suffix)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Retrieve inferred class if it exists. If not, nil is returned.
|
31
|
+
# @yield (optional) for further transforming name
|
32
|
+
# @yieldparam [String] name after suffix removed
|
33
|
+
# @yieldreturn [String] name after transformation
|
34
|
+
# @return [Class] inferred class
|
35
|
+
def inferred_class
|
36
|
+
name = extract_name
|
37
|
+
name = yield name if block_given?
|
38
|
+
name.constantize
|
39
|
+
rescue NameError => error
|
40
|
+
raise unless Inferrer.missing_name?(error, name)
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
# Detect if error is due to inferred class not existing, or some other error
|
46
|
+
def self.missing_name?(error, name)
|
47
|
+
return true if error.is_a? UnextractableError
|
48
|
+
missing_name = error.missing_name
|
49
|
+
length = [missing_name.length, name.length].min
|
50
|
+
missing_name[-length..-1] == name[-length..-1]
|
51
|
+
end
|
52
|
+
class UnextractableError < NameError; end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module StrongPresenter
|
2
|
+
module Permissible
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Permits all presenter attributes for presents, present & filter methods.
|
7
|
+
def permit!
|
8
|
+
define_method(:permitted_attributes){ @permitted_attributes ||= StrongPresenter::Permissions.new.permit_all! }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Permits given attributes. May be invoked multiple times.
|
13
|
+
#
|
14
|
+
# @example Each argument represents a single attribute:
|
15
|
+
# ArticlePresenter.new(@article).permit(:heading, :article)
|
16
|
+
#
|
17
|
+
# @example Attribute paths can be specified using symbol arrays. If an author name is normally accessed using @article.author.name:
|
18
|
+
# ArticlePresenter.new(@article).permit([:author, :name])
|
19
|
+
#
|
20
|
+
# @param [[Symbols*]*] attribute_paths
|
21
|
+
# the attributes to permit. An array of symbols represents an attribute path.
|
22
|
+
# @return [self]
|
23
|
+
def permit *attribute_paths
|
24
|
+
permitted_attributes.permit permissions_prefix, *attribute_paths
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
# Permits all presenter attributes for presents, present & filter methods.
|
29
|
+
def permit!
|
30
|
+
permitted_attributes.permit_all!
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Selects the attributes given which have been permitted - an array of attributes. Attributes are
|
35
|
+
# symbols, and attribute paths are arrays of symbols.
|
36
|
+
# @param [Array<Symbol>*] attribute_paths
|
37
|
+
# the attribute paths to check. The attribute paths may also have arguments.
|
38
|
+
# @return [Array<Array<Symbol>, Symbol>] attribute (paths)
|
39
|
+
def filter *attribute_paths
|
40
|
+
select_permitted(*attribute_paths).map{ |attribute| attribute.first if attribute.size == 1 } # un-pack symbol if array with single symbol
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def permitted_attributes
|
45
|
+
@permitted_attributes ||= StrongPresenter::Permissions.new
|
46
|
+
end
|
47
|
+
|
48
|
+
# Selects the attributes given which have been permitted - an array of attributes
|
49
|
+
# Each returned attribute paths will be an array, even if it consists of only 1 symbol
|
50
|
+
# @param [Array<Symbols>*] attribute_paths
|
51
|
+
# the attribute paths to check. The attribute paths may also have arguments.
|
52
|
+
# @return [Array<Array<Symbol>>] attribute (paths)
|
53
|
+
def select_permitted *attribute_paths
|
54
|
+
permitted_attributes.select_permitted permissions_prefix, *attribute_paths
|
55
|
+
end
|
56
|
+
|
57
|
+
# Links presenter to permissions group of given presenter.
|
58
|
+
# @param [Presenter] parent_presenter
|
59
|
+
# @param [Array<Symbol>] relative_path
|
60
|
+
# The prefix prepended before every permission check relative to parent presenter.
|
61
|
+
def link_permissions parent_presenter, relative_path = []
|
62
|
+
self.permissions_prefix = parent_presenter.send(:permissions_prefix) + Array(relative_path)
|
63
|
+
@permitted_attributes = parent_presenter.send(:permitted_attributes).merge @permitted_attributes, permissions_prefix
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
attr_writer :permissions_prefix
|
68
|
+
def permissions_prefix
|
69
|
+
@permissions_prefix ||= []
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|