detaso-oprah 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,149 @@
1
+ module Oprah
2
+ class Presenter < SimpleDelegator
3
+ # @return [ActionView::Base] The view context
4
+ attr_reader :view_context
5
+
6
+ alias :h :view_context
7
+
8
+ # @!visibility private
9
+ @@cache = ActiveSupport::Cache::MemoryStore.new
10
+
11
+ class << self
12
+ # Returns the shared presenter cache object.
13
+ #
14
+ # @return [ActiveSupport::Cache::MemoryStore]
15
+ def cache
16
+ @@cache
17
+ end
18
+
19
+ # Presents the given `object` with all it's matching presenters,
20
+ # following it's ancestors in reverse.
21
+ #
22
+ # @param object [Object] The object to present
23
+ # @param view_context [ActionView::Context] View context to assign
24
+ # @param only [Class] Class or Array of presenters to use
25
+ # @return [Presenter] Presented object
26
+ def present(object, view_context: default_view_context, only: nil)
27
+ presenters = presenter_classes_for(object)
28
+ presenters &= Array(only) if only
29
+
30
+ presenters.inject(object) do |memo, presenter|
31
+ presenter.new(memo, view_context: view_context)
32
+ end
33
+ end
34
+
35
+ # Presents the given `objects` with all their matching presenters.
36
+ # The behaviour and parameters are identical to `.present`'s.
37
+ #
38
+ # @param objects [Enumerable] The objects to present
39
+ # @see .present
40
+ def present_many(objects, **kwargs)
41
+ objects.map { |object| present(object, **kwargs) }
42
+ end
43
+
44
+ # Automatically wrap the objects returned by the given one-to-one
45
+ # `association` method in presenters.
46
+ #
47
+ # Presenters will re-use the parent's assigned view context.
48
+ #
49
+ # @param association [Symbol] Name of the association
50
+ # @return [Symbol] Name of the association
51
+ def presents_one(association)
52
+ define_method association do
53
+ present(__getobj__.__send__(association))
54
+ end
55
+
56
+ association
57
+ end
58
+
59
+ # Automatically wrap the objects returned by the given one-to-many
60
+ # or many-to-many `association` method in presenters.
61
+ #
62
+ # Presenters will re-use the parent's assigned view context.
63
+ #
64
+ # @param association [Symbol] Name of the association
65
+ # @return [Symbol] Name of the association
66
+ def presents_many(association)
67
+ define_method association do
68
+ present_many(__getobj__.__send__(association))
69
+ end
70
+
71
+ association
72
+ end
73
+
74
+ # Returns the default view context to use if no view context is explicitly
75
+ # passed to the presenter.
76
+ #
77
+ # @return [ActionView::Context]
78
+ def default_view_context
79
+ ActionController::Base.new.view_context
80
+ end
81
+
82
+ private
83
+
84
+ # @since 0.2.0
85
+ def presenter_classes_for(object)
86
+ klass = object.class
87
+
88
+ @@cache.fetch klass.name do
89
+ klass.ancestors.map do |ancestor|
90
+ (ancestor.name + "Presenter").safe_constantize if ancestor.name
91
+ end.compact.reverse
92
+ end
93
+ end
94
+ end
95
+
96
+ # Initializes a new Presenter.
97
+ #
98
+ # @param object [Object] The object to present
99
+ # @param view_context [ActionView::Context] View context to assign
100
+ def initialize(object, view_context: self.class.default_view_context)
101
+ __setobj__(object)
102
+ @view_context = view_context
103
+ end
104
+
105
+ # Presents a single object.
106
+ #
107
+ # Will re-use the presenter's assigned view context if no `view_context`:
108
+ # parameter is given.
109
+ #
110
+ # @see .present
111
+ def present(*args, **kwargs, &block)
112
+ kwargs = { view_context: view_context }.merge(kwargs)
113
+ self.class.present(*args, **kwargs, &block)
114
+ end
115
+
116
+ # Presents a collection of objects.
117
+ #
118
+ # Will re-use the presenter's assigned view context if no `view_context`:
119
+ # parameter is given.
120
+ #
121
+ # @see .present_many
122
+ def present_many(*args, **kwargs, &block)
123
+ kwargs = { view_context: view_context }.merge(kwargs)
124
+ self.class.present_many(*args, **kwargs, &block)
125
+ end
126
+
127
+ # Returns true if `klass` is the class of the presented object or the
128
+ # presenter, or if `#class` is one of the superclasses of the presented
129
+ # object, the presenter or modules included in the presented object or the
130
+ # presenter.
131
+ #
132
+ # @param other [Module]
133
+ # @return [Boolean] result
134
+ def kind_of?(other)
135
+ super || __getobj__.kind_of?(other)
136
+ end
137
+
138
+ alias :is_a? :kind_of?
139
+
140
+ # Returns `true` if the presented object or the presenter is an instance
141
+ # of the given `class`.
142
+ #
143
+ # @param klass [Class]
144
+ # @return [Boolean] result
145
+ def instance_of?(klass)
146
+ super || __getobj__.instance_of?(klass)
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails/railtie'
2
+
3
+ module Oprah
4
+ class Railtie < Rails::Railtie
5
+ initializer "oprah.configure_cache_clear_on_code_reload" do
6
+ ActiveSupport::Reloader.to_run do
7
+ Oprah::Presenter.cache.clear
8
+
9
+ if Oprah.debug?
10
+ Rails.logger.debug "Oprah cache cleared"
11
+ end
12
+ end
13
+ end
14
+
15
+ initializer "oprah.configure_action_controller_helpers" do
16
+ ActiveSupport.on_load :action_controller do
17
+ ActionController::Base.include(Oprah::ControllerHelpers)
18
+ end
19
+ end
20
+
21
+ initializer "oprah.configure_action_mailer_helpers" do
22
+ ActiveSupport.on_load :action_mailer do
23
+ ActionMailer::Base.include(Oprah::ControllerHelpers)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ module Oprah
2
+ # Test helpers that can be included into `Minitest::Test` or
3
+ # `ActiveSupport::TestCase`.
4
+ #
5
+ # @since 0.1.2
6
+ module TestHelpers
7
+ # Presents a collection of objects.
8
+ #
9
+ # @see Presenter.present_many
10
+ def present_many(*args, **kwargs, &block)
11
+ Presenter.present_many(*args, **kwargs, &block)
12
+ end
13
+
14
+ # Presents a single object.
15
+ #
16
+ # @see Presenter.present
17
+ def present(*args, **kwargs, &block)
18
+ Presenter.present(*args, **kwargs, &block)
19
+ end
20
+
21
+ # Fails unless `object` is a presenter.
22
+ #
23
+ # @since 0.1.3
24
+ # @param [Object] object The object to be tested
25
+ # @return [Boolean]
26
+ def assert_presented(object)
27
+ msg = message(msg) do
28
+ "Expected #{mu_pp(object)} to be an Oprah::Presenter"
29
+ end
30
+
31
+ assert object.kind_of?(Oprah::Presenter), msg
32
+ end
33
+
34
+ # Fails if `object` is a presenter.
35
+ #
36
+ # @since 0.1.3
37
+ # @param [Object] object The object to be tested
38
+ # @return [Boolean]
39
+ def refute_presented(object)
40
+ msg = message(msg) do
41
+ "Expected #{mu_pp(object)} to not be an Oprah::Presenter"
42
+ end
43
+
44
+ refute object.kind_of?(Oprah::Presenter), msg
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ module Oprah
2
+ # @return [String] The Oprah library version.
3
+ VERSION = "0.3.0"
4
+ end
data/lib/oprah.rb ADDED
@@ -0,0 +1,43 @@
1
+ # stdlib
2
+ require 'delegate'
3
+
4
+ # gems
5
+ require 'active_support/cache'
6
+ require 'active_support/concern'
7
+ require 'active_support/inflector'
8
+ require 'active_support/proxy_object'
9
+ require 'action_controller'
10
+
11
+ # internal
12
+ require 'oprah/controller_helpers'
13
+ require 'oprah/presenter'
14
+ require 'oprah/version'
15
+
16
+ require 'oprah/railtie' if defined?(Rails)
17
+
18
+ # The Oprah namespace.
19
+ #
20
+ # @since 0.0.1
21
+ module Oprah
22
+ # @!visibility private
23
+ def debug?
24
+ !!ENV["OPRAH_DEBUG"]
25
+ end
26
+
27
+ # Presents a single object.
28
+ #
29
+ # @see Presenter.present
30
+ def present(*args, **kwargs, &block)
31
+ Presenter.present(*args, **kwargs, &block)
32
+ end
33
+
34
+ # Presents a collection of objects.
35
+ #
36
+ # @see Presenter.present_many
37
+ def present_many(*args, **kwargs, &block)
38
+ Presenter.present_many(*args, **kwargs, &block)
39
+ end
40
+
41
+ extend self
42
+ end
43
+
data/oprah.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oprah/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "detaso-oprah"
8
+ gem.version = Oprah::VERSION
9
+ gem.authors = ["Tobias Svensson"]
10
+ gem.email = ["tob@tobiassvensson.co.uk"]
11
+ gem.summary = "Opinionated presenters for Rails 5 - without the cruft"
12
+ gem.description = gem.summary
13
+ gem.homepage = "https://github.com/detaso/oprah"
14
+ gem.license = "MIT"
15
+
16
+ gem.files = `git ls-files -z`.split("\x0")
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_dependency "activesupport", ">= 5.0.0"
22
+ gem.add_dependency "actionpack", ">= 5.0.0"
23
+
24
+ gem.add_development_dependency "bundler", "~> 2"
25
+ gem.add_development_dependency "minitest", "~> 5.9.0"
26
+ gem.add_development_dependency "rails", ">= 5.0.0"
27
+ gem.add_development_dependency "rake", "~> 10.0"
28
+ gem.add_development_dependency "redcarpet", "~> 3.3.4"
29
+ gem.add_development_dependency "yard", "~> 0.9.5"
30
+ gem.add_development_dependency "sqlite3", "~> 1.3.11"
31
+ end
data/tasks/bundler.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_helper'
2
+
3
+ namespace :gem do
4
+ Bundler::GemHelper.install_tasks name: 'oprah'
5
+ end
data/tasks/console.rb ADDED
@@ -0,0 +1,35 @@
1
+ class RakeConsole
2
+ GEM = Dir["*.gemspec"].first.sub('.gemspec', '')
3
+ REQUIRE_PATH = File.join(Dir.pwd, 'lib', GEM)
4
+
5
+ module Helpers
6
+ def reload!
7
+ puts "Reloading..."
8
+ $LOADED_FEATURES.select do |feat|
9
+ feat =~ /\/#{GEM}\//
10
+ end.each { |file| load file }
11
+ true
12
+ end
13
+ end
14
+
15
+ def start
16
+ require REQUIRE_PATH
17
+ ARGV.clear
18
+ Object.include(Helpers)
19
+
20
+ begin
21
+ require 'pry'
22
+ TOPLEVEL_BINDING.pry
23
+ rescue LoadError
24
+ require 'irb'
25
+ require 'irb/completion'
26
+ IRB.start
27
+ end
28
+ end
29
+ end
30
+
31
+ desc "Start development console"
32
+ task :console do
33
+ RakeConsole.new.start
34
+ end
35
+
data/tasks/minitest.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << "test"
5
+ t.test_files = FileList['test/**/*_test.rb']
6
+ t.verbose = false
7
+ end
8
+
data/tasks/yard.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'yard'
2
+ require 'yard/rake/yardoc_task'
3
+
4
+ YARD::Rake::YardocTask.new(:doc) do |t|
5
+ t.files = ['lib/**/*.rb']
6
+ t.options = %w{
7
+ --verbose
8
+ --markup markdown
9
+ --readme README.md
10
+ --tag comment
11
+ --hide-tag comment
12
+ --hide-void-return
13
+ -M
14
+ redcarpet
15
+ }
16
+ end
@@ -0,0 +1,10 @@
1
+ require 'helper'
2
+
3
+ class PostsControllerTest < ActionController::TestCase
4
+ def test_show
5
+ get :show, params: { id: 1 }
6
+
7
+ expected = "<p>\n Hello!\n</p>"
8
+ assert_equal expected, response.body
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationController < ActionController::Base
2
+ include Oprah::ControllerHelpers
3
+ end
@@ -0,0 +1,5 @@
1
+ class PostsController < ApplicationController
2
+ def show
3
+ @post = present Post.new
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ end
@@ -0,0 +1,8 @@
1
+ class PostMailer < ApplicationMailer
2
+ default from: 'bar@baz', to: 'foo@bar'
3
+
4
+ def notification(post)
5
+ @post = present(post)
6
+ mail(subject: 'Foobar')
7
+ end
8
+ end
@@ -0,0 +1,2 @@
1
+ class Post
2
+ end
@@ -0,0 +1,5 @@
1
+ class PostPresenter < Oprah::Presenter
2
+ def paragraph(&block)
3
+ h.content_tag(:p, &block)
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ <%= @post.paragraph do %>
2
+ Hello!
3
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= @post.paragraph do %>
2
+ Hello!
3
+ <% end %>
@@ -0,0 +1,6 @@
1
+
2
+ test:
3
+ adapter: sqlite3
4
+ database: ':memory:'
5
+ pool: 5
6
+ timeout: 5000
@@ -0,0 +1,3 @@
1
+ Dummy::Application.routes.draw do
2
+ resources :posts, only: [:show]
3
+ end
@@ -0,0 +1,29 @@
1
+ ENV['RAILS_ENV'] ||= 'test'
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __FILE__)
3
+ require 'bundler/setup'
4
+ require 'rails/all'
5
+ Bundler.require(:default, Rails.env)
6
+
7
+ module Dummy
8
+ class Application < ::Rails::Application
9
+ config.root = File.join __FILE__, '..'
10
+ config.cache_store = :memory_store
11
+ config.assets.enabled = false
12
+ config.active_support.test_order = :random
13
+ config.consider_all_requests_local = true
14
+ config.action_controller.perform_caching = false
15
+ config.action_dispatch.show_exceptions = false
16
+ config.action_controller.allow_forgery_protection = false
17
+ config.action_mailer.delivery_method = :test
18
+ config.active_support.deprecation = :stderr
19
+ config.allow_concurrency = true
20
+ config.cache_classes = true
21
+ config.dependency_loading = true
22
+ config.preload_frameworks = true
23
+ config.eager_load = true
24
+ config.secret_key_base = '012345678901234567890123456789'
25
+ end
26
+ end
27
+
28
+ Dummy::Application.initialize!
29
+ require 'rails/test_help'
File without changes
data/test/helper.rb ADDED
@@ -0,0 +1,84 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+ require 'oprah'
4
+ require 'oprah/railtie'
5
+ require 'oprah/test_helpers'
6
+ require 'dummy/init'
7
+
8
+ class Minitest::Test
9
+ include Oprah::TestHelpers
10
+
11
+ def setup
12
+ super
13
+ Oprah::Presenter.cache.clear
14
+ end
15
+ end
16
+
17
+ module Fixtures
18
+ module Entity
19
+ end
20
+
21
+ class EntityPresenter < Oprah::Presenter
22
+ def foo
23
+ "foo"
24
+ end
25
+ end
26
+
27
+ class User
28
+ include Entity
29
+
30
+ def first_name
31
+ "Foo"
32
+ end
33
+
34
+ def last_name
35
+ "Bar"
36
+ end
37
+
38
+ private
39
+
40
+ def password
41
+ "baz"
42
+ end
43
+ end
44
+
45
+ class UserPresenter < Oprah::Presenter
46
+ def name
47
+ [first_name, last_name].join(' ')
48
+ end
49
+
50
+ def foo
51
+ super + "bar"
52
+ end
53
+ end
54
+
55
+ class Comment
56
+ end
57
+
58
+ class CommentPresenter < Oprah::Presenter
59
+ end
60
+
61
+ class Project
62
+ def comments
63
+ Array.new(3) { Comment.new }
64
+ end
65
+
66
+ def owner
67
+ User.new
68
+ end
69
+ end
70
+
71
+ class ProjectPresenter < Oprah::Presenter
72
+ presents_many :comments
73
+ presents_one :owner
74
+ end
75
+
76
+ # EigenUser contains eigen class in ancestors list.
77
+ #
78
+ # > EigenUser.ancestors
79
+ # => [Fixtures::EigenUser, #<Module:0x007faa79b128f0>, Object, ... , Kernel, BasicObject]
80
+ #
81
+ class EigenUser
82
+ include Module.new
83
+ end
84
+ end
@@ -0,0 +1,13 @@
1
+ class PostMailerTest < ActionMailer::TestCase
2
+ def test_invite
3
+ post = Post.new
4
+ email = PostMailer.notification(Post.new)
5
+
6
+ assert_emails 1 do
7
+ email.deliver_now
8
+ end
9
+
10
+ expected = "<p>\n Hello!\n</p>"
11
+ assert_equal expected, email.body.to_s
12
+ end
13
+ end
@@ -0,0 +1,65 @@
1
+ require 'helper'
2
+
3
+ module Oprah
4
+ class ControllerHelpersTest < Minitest::Test
5
+ class Controller
6
+ @@helper_methods = []
7
+
8
+ class << self
9
+ def helper_methods
10
+ @@helper_methods
11
+ end
12
+
13
+ def helper_method(method)
14
+ @@helper_methods << method
15
+ end
16
+ end
17
+
18
+ def view_context
19
+ :ok
20
+ end
21
+
22
+ include Oprah::ControllerHelpers
23
+ end
24
+
25
+ include Fixtures
26
+
27
+ def setup
28
+ super
29
+ @controller = Controller.new
30
+ end
31
+
32
+ def test_present
33
+ presenter = @controller.present(User.new)
34
+
35
+ assert_kind_of UserPresenter, presenter
36
+ assert_kind_of EntityPresenter, presenter
37
+
38
+ assert_equal "ok", presenter.view_context.to_s
39
+ end
40
+
41
+ def test_present_custom_view_context
42
+ presenter = @controller.present(User.new, view_context: :foobar)
43
+ assert_equal :foobar, presenter.view_context
44
+ end
45
+
46
+ def test_present_many
47
+ presenters = @controller.present_many([User.new, User.new])
48
+
49
+ assert_equal 2, presenters.length
50
+
51
+ presenters.each do |presenter|
52
+ assert_equal "Foo Bar", presenter.name
53
+ assert_equal "ok", presenter.view_context.to_s
54
+ end
55
+ end
56
+
57
+ def test_helper_method
58
+ assert_equal [:present, :present_many], Controller.helper_methods
59
+ end
60
+
61
+ def test_oprah_view_context
62
+ assert_equal @controller.oprah_view_context, @controller.view_context
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,17 @@
1
+ require 'helper'
2
+
3
+ module Oprah
4
+ class Test < Minitest::Test
5
+ def test_debug
6
+ old = ENV["OPRAH_DEBUG"]
7
+
8
+ ENV["OPRAH_DEBUG"] = nil
9
+ refute Oprah.debug?
10
+
11
+ ENV["OPRAH_DEBUG"] = "1"
12
+ assert Oprah.debug?
13
+
14
+ ENV["OPRAH_DEBUG"] = old
15
+ end
16
+ end
17
+ end