detaso-oprah 0.3.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.
@@ -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