gestart-showcase 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2fd017b78a3588a69ad0bb67759b8ce42f54ceea685a568bf7f37fd74297a602
4
+ data.tar.gz: 284a480294e3603197899f9143f1414daeb94b0159a4d5bdccacb6139a69fbe6
5
+ SHA512:
6
+ metadata.gz: 595db0c75c8855199d60a7bc6ca9a07cec8d6d96855f93fea7d540ea19f7b524ed5cc83c0a7339951a5d1f12fd3dc8e9a53d23df676f00f2ee4026e162a4885e
7
+ data.tar.gz: b393e943f26c68e32d1703c3e71c5000c5e617f3bc1d55d3664fc98056b8655179a1a5e6a19cdd0f34e7e9819fe9cb009c5a5a92116751faa8b08773955d3e10
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ rspec:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: '3.4.9'
16
+ bundler-cache: true
17
+ - run: bundle exec rspec
18
+
19
+ rspec-frozen:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: '3.4.9'
26
+ bundler-cache: true
27
+ - run: bundle exec rspec
28
+ env:
29
+ RUBYOPT: '--enable-frozen-string-literal'
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ gemfiles/*.gemfile.lock
19
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+
2
+
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.4.9
data/CHANGELOG.md ADDED
@@ -0,0 +1,49 @@
1
+ # v1.0.0 — the fork
2
+
3
+ Gestart fork of [`showcase`](https://github.com/welaika/showcase) 0.2.5, the last
4
+ upstream release, dated 16/03/2014.
5
+
6
+ **Why.** `Showcase::Presenter` sits under roughly 430 call sites in Gestart, and
7
+ it arrived as a dependency of `admino` rather than as a choice. Once admino was
8
+ forked, this was the last third-party code left in the rendering path of every
9
+ index page and every presenter. Rewriting it was rejected: the presenter is
10
+ already a `SimpleDelegator`, but one that overrides `class`, `kind_of?`, `is_a?`
11
+ and `instance_of?` so they answer as the record does — `form_for`, `render`,
12
+ `polymorphic_path` and `dom_id` all lean on that, and no test covers it.
13
+
14
+ The full decision lives in the application repo: `docs/decisioni/admino.md`.
15
+
16
+ **What changed from 0.2.5.**
17
+
18
+ * Pruned what Gestart never calls: the `LinkTo`, `Seo` and `Share` traits, the
19
+ `SeoMetaBuilder` and `FirstNonblank` helpers, `Traits::Base` (which only those
20
+ traits used) and the presenter generator. Their specs went with them.
21
+ * `Traits::Record` **stays whole**, `box` included. It was listed as dead and it
22
+ is not: `dom_id` reaches the rendered page through admino's table presenter,
23
+ which asks `resource.respond_to?(:dom_id)` and, when it does, writes an `id` on
24
+ every row. Removing it would have silently dropped `id="tool_1"` from every
25
+ index table.
26
+ * `traits/record.rb` now requires `ConfigObject` and `HtmlOptions` explicitly.
27
+ Upstream got them for free because `link_to.rb` required them first; with that
28
+ file gone, the constants stopped loading.
29
+ * Suite runnable on Ruby 3.4 and Rails 8.1: `RailsViewContext` is built with an
30
+ explicit lookup context, `RSpecHtmlMatchers` is included explicitly, and
31
+ `appraisal`, `coveralls` and the Rails 3.2/4 gemfiles are gone.
32
+
33
+ **Maintenance.** This fork gets repaired, not extended: it is fixed when Rails
34
+ breaks it, and nothing more.
35
+
36
+ ---
37
+
38
+ # Version 0.2.5
39
+
40
+ * Smarter HtmlOptions (extracted CSS class logic to HtmlClassAttribute)
41
+
42
+ # Version 0.2.4
43
+
44
+ * Added missing ActiveSupport requires
45
+
46
+ # Version 0.2.3
47
+
48
+ * Fix missing namespace in HtmlOptions and FirstNonblank helpers
49
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Stefano Verna
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # gestart-showcase
2
+
3
+ A simple but powerful exhibit/presenter implementation.
4
+
5
+ > **This is a maintenance fork.** `gestart-showcase` is Gestart's fork of
6
+ > [`showcase`](https://github.com/welaika/showcase) 0.2.5, whose last upstream
7
+ > release is dated 16/03/2014. It exists because the presenter layer sits under
8
+ > roughly 430 call sites in Gestart, and upstream will get no further fixes.
9
+ >
10
+ > **It gets repaired, not extended.** It is fixed when Rails breaks it, and
11
+ > nothing more.
12
+ >
13
+ > Compared to upstream: the namespace is `GestartShowcase::`, and the `LinkTo`,
14
+ > `Seo` and `Share` traits, the `SeoMetaBuilder` and `FirstNonblank` helpers and
15
+ > the presenter generator are gone — Gestart never called them. `Traits::Record`
16
+ > stays, `box` included. See [`CHANGELOG.md`](CHANGELOG.md).
17
+
18
+ ## Why should I use presenters in my Rails app?
19
+
20
+ See [Avdi's Exhibits introductory post](http://devblog.avdi.org/2012/06/04/displaycase-gem-now-available/).
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'gestart-showcase'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ ```
33
+ $ bundle
34
+ ```
35
+
36
+ Or install it yourself as:
37
+
38
+ ```
39
+ $ gem install gestart-showcase
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ With Rails, you're already set, move on! With Padrino, include `GestartShowcase::Helpers::Present`
45
+ in your app `helpers` block.
46
+
47
+ ```ruby
48
+ helpers do
49
+ include GestartShowcase::Helpers::Present
50
+ end
51
+ ```
52
+
53
+ You can now instantiate new presenters in your controller/views using the
54
+ included helpers:
55
+
56
+ ```ruby
57
+ # this is the object that needs to be presented
58
+ person = Person.new
59
+
60
+ # automatically infers presenter class to use based on person's class name
61
+ present(person) # => returns a PersonPresenter instance
62
+
63
+ # you can also explicitly tell what presenter to use
64
+ present(person, AdminPresenter) # => returns an AdminPresenter instance
65
+
66
+ # explicit presenter and context
67
+ present(person, PersonPresenter, context)
68
+
69
+ # maps each person in the collection with a presenter
70
+ present_collection([person]) # => returns an array of PersonPresenters
71
+ ```
72
+
73
+ Define your presenters i.e. in a `app/presenters` folder:
74
+
75
+ ```ruby
76
+ class ProjectPresenter < GestartShowcase::Presenter
77
+ # automatically wraps the attribute into a PersonPresenter
78
+ presents :person
79
+
80
+ # automatically wraps the attribute into an AdminPresenter
81
+ presents :person, with: AdminPresenter
82
+
83
+ # expects project.task to return an enumerable. automatically wraps each task
84
+ # in a TaskPresenter presenter
85
+ presents_collection :tasks
86
+
87
+ # you can use `view_context`, or the shortcut `h`, to access the context.
88
+ # `object` refers to the object being presented
89
+ def title
90
+ h.link_to object.title, object
91
+ end
92
+ end
93
+ ```
94
+
95
+ ## Rails
96
+
97
+ ### Traits
98
+
99
+ Please [read the tests](https://github.com/gestartcloudsrl/gestart-showcase/tree/main/lib/gestart_showcase/traits)
100
+ for a detailed explanation of each method available.
101
+
102
+ #### `GestartShowcase::Traits::Record`
103
+
104
+ To be used to present ActiveModel-based records. Inside your presenter, include
105
+ the trait like this:
106
+
107
+ ```ruby
108
+ class ProjectPresenter < GestartShowcase::Presenter
109
+ include GestartShowcase::Traits::Record
110
+ end
111
+ ```
112
+
113
+ ##### `#dom_id`
114
+
115
+ ```ruby
116
+ present(@project).dom_id # => "project_12"
117
+ ```
118
+
119
+ ##### `#dom_class`
120
+
121
+ ```ruby
122
+ present(@project).dom_class # => "project"
123
+ ```
124
+
125
+ ##### `#box`
126
+
127
+ Super useful in acceptance testing to check the presence of a record inside a
128
+ view:
129
+
130
+ ```erb
131
+ <% present(@project).box(class: 'big') do %>
132
+ <p>Hi there!</p>
133
+ <% end %>
134
+ ```
135
+
136
+ Produces the following:
137
+
138
+ ```html
139
+ <div class="project big" id="project_12">
140
+ <p>Hi there</p>
141
+ </div>
142
+ ```
143
+
144
+ Additional HTML attributes can be optionally specified within a config block
145
+ inside the presenter:
146
+
147
+ ```ruby
148
+ class ProjectPresenter < GestartShowcase::Presenter
149
+ include GestartShowcase::Traits::Record
150
+
151
+ box do |c|
152
+ c.html_options class: 'another-class', role: 'project'
153
+ end
154
+ end
155
+ ```
156
+
157
+ ## Testing
158
+
159
+ Install gems:
160
+
161
+ ```
162
+ $ bundle
163
+ $ rake appraisal:install
164
+ ```
165
+
166
+ Launch tests:
167
+
168
+ ```
169
+ rake appraisal
170
+ ```
171
+
172
+
173
+ ## Contributing
174
+
175
+ 1. Fork it
176
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
177
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
178
+ 4. Push to the branch (`git push origin my-new-feature`)
179
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'appraisal'
4
+
5
+ desc 'Default: run specs'
6
+ task default: :spec
7
+
8
+ require 'rspec/core/rake_task'
9
+ RSpec::Core::RakeTask.new do |t|
10
+ t.pattern = "spec/**/*_spec.rb"
11
+ end
12
+
13
+ Bundler::GemHelper.install_tasks
14
+
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gestart_showcase/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'gestart-showcase'
8
+ gem.version = GestartShowcase::VERSION
9
+ gem.authors = ['Gestart Cloud S.r.l.', 'Stefano Verna']
10
+ gem.email = ['dev@gestart.net']
11
+ gem.description = %q{Gestart fork of showcase 0.2.5, pruned of what Gestart does not use}
12
+ gem.summary = %q{Gestart fork of showcase: the presenter layer behind every Gestart view}
13
+ gem.homepage = 'https://github.com/gestartcloudsrl/gestart-showcase'
14
+ gem.license = 'MIT'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.required_ruby_version = '>= 3.1'
22
+
23
+ gem.add_dependency 'activesupport'
24
+
25
+ gem.add_development_dependency 'rspec'
26
+ gem.add_development_dependency 'rails'
27
+ gem.add_development_dependency 'rspec-html-matchers'
28
+ gem.add_development_dependency 'ostruct'
29
+ end
@@ -0,0 +1 @@
1
+ require 'gestart_showcase'
@@ -0,0 +1,5 @@
1
+ module GestartShowcase
2
+ class PresenterClassNotFound < RuntimeError
3
+ end
4
+ end
5
+
@@ -0,0 +1,28 @@
1
+ require 'ostruct'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ class ConfigObject
6
+ def initialize(context, &block)
7
+ @config_block = block
8
+ @context = context
9
+ end
10
+
11
+ def to_struct
12
+ OpenStruct.new(to_hash)
13
+ end
14
+
15
+ def to_hash
16
+ @result = {}
17
+ @context.instance_exec(self, &@config_block)
18
+ @result.symbolize_keys
19
+ end
20
+
21
+ def method_missing(name, *args, &block)
22
+ name = name.to_s.gsub(/=$/, '')
23
+ @result[name.to_sym] = args.first
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,23 @@
1
+ module GestartShowcase
2
+ module Helpers
3
+ class HtmlClassAttribute
4
+ def initialize(value = nil)
5
+ @css_classes = value.to_s.strip.split(/\s+/)
6
+ end
7
+
8
+ def <<(value)
9
+ value = value.to_s.strip.split(/\s+/)
10
+ @css_classes += value
11
+ end
12
+
13
+ def to_html_attribute
14
+ if @css_classes.any?
15
+ @css_classes.uniq.join(' ')
16
+ else
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,32 @@
1
+ require 'gestart_showcase/helpers/html_class_attribute'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ class HtmlOptions
6
+ def initialize(options = {})
7
+ @options = (options || {}).symbolize_keys
8
+ end
9
+
10
+ def add_class!(css_class)
11
+ merge_attrs!(class: css_class)
12
+ end
13
+
14
+ def merge_attrs!(options = {})
15
+ new_options = (options || {}).symbolize_keys
16
+
17
+ if new_options[:class]
18
+ class_attr = HtmlClassAttribute.new(@options[:class])
19
+ class_attr << new_options[:class]
20
+ new_options[:class] = class_attr.to_html_attribute
21
+ end
22
+
23
+ @options.merge!(new_options)
24
+ end
25
+
26
+ def to_h
27
+ @options.dup
28
+ end
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,21 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ module ModuleMethodBuilder
6
+ def define_module_method(name_chunks, &block)
7
+ method_name = Array(name_chunks).map(&:to_s).map(&:presence).compact.join("_")
8
+ if method_defined?(method_name)
9
+ false
10
+ else
11
+ method_module = Module.new do
12
+ define_method(method_name, &block)
13
+ end
14
+ include(method_module)
15
+ true
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,50 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/inflector'
3
+ require 'active_support/core_ext/hash/keys'
4
+
5
+ require 'gestart_showcase/errors'
6
+
7
+ module GestartShowcase
8
+ module Helpers
9
+ module Present
10
+ extend ActiveSupport::Concern
11
+
12
+ def presenter_context
13
+ if respond_to? :view_context
14
+ view_context
15
+ else
16
+ self
17
+ end
18
+ end
19
+
20
+ def present(obj, klass = nil, context = presenter_context, options = {})
21
+ options.assert_valid_keys(:nil_presenter)
22
+
23
+ if obj || options.fetch(:nil_presenter, false)
24
+ klass ||= presenter_class(obj)
25
+ if klass.nil?
26
+ raise GestartShowcase::PresenterClassNotFound, "Unable to guess a presenter class for #{obj.inspect}!"
27
+ end
28
+ klass.new(obj, context)
29
+ else
30
+ nil
31
+ end
32
+ end
33
+
34
+ def present_collection(obj, klass = nil, context = presenter_context, options = {})
35
+ obj.map { |o| present(o, klass, context, options) }
36
+ end
37
+
38
+ private
39
+
40
+ def presenter_class(obj)
41
+ (obj.class.ancestors - obj.class.included_modules).each do |k|
42
+ klass = "#{k.name}Presenter".safe_constantize
43
+ return klass if klass
44
+ end
45
+ nil
46
+ end
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,64 @@
1
+ require 'delegate'
2
+ require 'gestart_showcase/helpers/present'
3
+ require 'gestart_showcase/helpers/module_method_builder'
4
+ require 'active_support/core_ext/array/extract_options'
5
+ require 'active_support/core_ext/hash/keys'
6
+
7
+ module GestartShowcase
8
+ class Presenter < SimpleDelegator
9
+ include Helpers::Present
10
+ extend Helpers::ModuleMethodBuilder
11
+
12
+ attr_reader :view_context
13
+
14
+ alias_method :object, :__getobj__
15
+ alias_method :h, :view_context
16
+ alias_method :try, :__send__
17
+ alias_method :__decorator_class__, :class
18
+
19
+ def initialize(obj, context)
20
+ super(obj)
21
+ @view_context = context
22
+ end
23
+
24
+ def class
25
+ object.class
26
+ end
27
+
28
+ def kind_of?(klass)
29
+ object.kind_of?(klass)
30
+ end
31
+ alias_method :is_a?, :kind_of?
32
+
33
+ def instance_of?(klass)
34
+ object.instance_of?(klass)
35
+ end
36
+
37
+ def self.presents(*args)
38
+ wrap_methods(args, :present)
39
+ end
40
+
41
+ def self.presents_collection(*args)
42
+ wrap_methods(args, :present_collection)
43
+ end
44
+
45
+ private
46
+
47
+ def self.wrap_methods(args, method)
48
+ options = args.extract_options!
49
+ options.assert_valid_keys(:with, :nil_presenter)
50
+ presenter_klass = options.fetch(:with, nil)
51
+
52
+ args.each do |attr|
53
+ define_module_method attr do
54
+ send(method,
55
+ object.send(attr),
56
+ presenter_klass,
57
+ view_context,
58
+ options.slice(:nil_presenter))
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,12 @@
1
+ require 'gestart_showcase/traits'
2
+
3
+ module GestartShowcase
4
+ class Railtie < Rails::Railtie
5
+ initializer "action_view.initialize_showcase" do
6
+ ActiveSupport.on_load(:action_view) do
7
+ ActionView::Base.send :include, GestartShowcase::Helpers::Present
8
+ end
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,64 @@
1
+ require 'gestart_showcase/helpers/config_object'
2
+ require 'gestart_showcase/helpers/html_options'
3
+
4
+ module GestartShowcase
5
+ module Traits
6
+
7
+ module Record
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def box(&block)
12
+ @box_config_block = block
13
+ end
14
+
15
+ def __box_config_block__
16
+ @box_config_block
17
+ end
18
+ end
19
+
20
+ def dom_id
21
+ record_identifier.dom_id(self)
22
+ end
23
+
24
+ def dom_class
25
+ record_identifier.dom_class(self)
26
+ end
27
+
28
+ def box(*args, &block)
29
+ options = args.extract_options!
30
+ tag = args.pop || :div
31
+
32
+ config_block = self.__decorator_class__.__box_config_block__
33
+ config_options = if config_block
34
+ Helpers::ConfigObject.new(self, &config_block).to_struct.html_options
35
+ else
36
+ {}
37
+ end
38
+
39
+ html_options = Helpers::HtmlOptions.new(config_options)
40
+ html_options.merge_attrs!(options)
41
+ html_options.add_class!(dom_class)
42
+ html_options.merge_attrs!(id: dom_id)
43
+
44
+ h.content_tag(tag, html_options.to_h) do
45
+ h.capture(self, &block)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def record_identifier
52
+ if defined?(ActionView::RecordIdentifier)
53
+ ActionView::RecordIdentifier
54
+ elsif defined?(ActionController::RecordIdentifier)
55
+ ActionController::RecordIdentifier
56
+ else
57
+ raise 'No RecordIdentifier found!'
58
+ end
59
+ end
60
+ end
61
+
62
+ end
63
+ end
64
+
@@ -0,0 +1,6 @@
1
+ require 'gestart_showcase/traits/record'
2
+
3
+ module GestartShowcase
4
+ module Traits
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module GestartShowcase
2
+ VERSION = "1.0.0"
3
+ end
4
+
@@ -0,0 +1,7 @@
1
+ require 'gestart_showcase/version'
2
+ require 'gestart_showcase/presenter'
3
+ require 'gestart_showcase/railtie' if defined?(Rails)
4
+
5
+ module GestartShowcase
6
+ end
7
+
data/spec/fixtures.rb ADDED
@@ -0,0 +1,112 @@
1
+ class RailsViewContext < ActionView::Base
2
+ include ActionView::Helpers::TagHelper
3
+
4
+ def initialize(*)
5
+ super(ActionView::LookupContext.new([]), {}, nil)
6
+ end
7
+
8
+ def capture(*args, &block)
9
+ block.call(*args)
10
+ end
11
+ end
12
+
13
+ class Model
14
+ extend ActiveModel::Naming
15
+
16
+ def to_key
17
+ Array('1')
18
+ end
19
+ end
20
+
21
+ class Person < Struct.new(:name)
22
+ end
23
+
24
+ class Customer < Person
25
+ end
26
+
27
+ class EnterpriseCustomer < Customer
28
+ end
29
+
30
+ class Project < Struct.new(:name)
31
+ def owner
32
+ Person.new("Stefano Verna")
33
+ end
34
+
35
+ def collaborators
36
+ [ Person.new("Ju Liu") ]
37
+ end
38
+
39
+ def first_collaborator
40
+ collaborators.first
41
+ end
42
+
43
+ def owner_child
44
+ end
45
+
46
+ def foobar
47
+ end
48
+
49
+ def dummy
50
+ "foobar"
51
+ end
52
+ end
53
+
54
+ class PersonPresenter < GestartShowcase::Presenter
55
+ def sex
56
+ 'male'
57
+ end
58
+
59
+ def bold_name
60
+ h.bold(object.name)
61
+ end
62
+ end
63
+
64
+ class AdminPresenter < PersonPresenter
65
+ def bold_name
66
+ h.bold("Admin #{object.name}")
67
+ end
68
+ end
69
+
70
+ class ProjectPresenter < GestartShowcase::Presenter
71
+ presents :owner, with: AdminPresenter
72
+ presents_collection :collaborators
73
+ presents :owner_child
74
+ presents :foobar, nil_presenter: true
75
+
76
+ def name
77
+ "Presented #{object.name}"
78
+ end
79
+
80
+ def owner
81
+ super
82
+ end
83
+
84
+ def bold_name
85
+ h.bold(object.name)
86
+ end
87
+
88
+ def first_collaborator
89
+ present(object.first_collaborator)
90
+ end
91
+ end
92
+
93
+ class NilClassPresenter < GestartShowcase::Presenter
94
+ def hi
95
+ "hi!"
96
+ end
97
+ end
98
+
99
+ class Context
100
+ include GestartShowcase::Helpers::Present
101
+
102
+ def bold(text)
103
+ "**#{text}**"
104
+ end
105
+ end
106
+
107
+ class Shop
108
+ def owner
109
+ Person.new("Stefano Verna")
110
+ end
111
+ end
112
+
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ describe HtmlClassAttribute do
6
+ let(:attribute) { HtmlClassAttribute.new(string) }
7
+ let(:string) { nil }
8
+
9
+ describe '.to_html_attribute' do
10
+ subject { attribute.to_html_attribute }
11
+
12
+ context 'nil' do
13
+ let(:string) { nil }
14
+ it { should be_nil }
15
+ end
16
+
17
+ context 'empty' do
18
+ let(:string) { '' }
19
+ it { should be_nil }
20
+ end
21
+
22
+ context 'whitespaces only' do
23
+ let(:string) { ' ' }
24
+ it { should be_nil }
25
+ end
26
+
27
+ context 'with trailing spaces' do
28
+ let(:string) { ' foo ' }
29
+ it { should eq 'foo' }
30
+ end
31
+
32
+ context 'with multiple classes' do
33
+ let(:string) { ' foo bar ' }
34
+ it { should eq 'foo bar' }
35
+ end
36
+ end
37
+
38
+ describe '#<<' do
39
+ before do
40
+ attribute << addition
41
+ end
42
+
43
+ subject { attribute.to_html_attribute }
44
+
45
+ context 'with no existing class attribute' do
46
+ let(:string) { nil }
47
+ let(:addition) { ' foo ' }
48
+
49
+ it { should eq 'foo' }
50
+ end
51
+
52
+ context 'with empty class attribute' do
53
+ let(:string) { '' }
54
+ let(:addition) { 'foo' }
55
+
56
+ it { should eq 'foo' }
57
+ end
58
+
59
+ context 'with existing class attribute' do
60
+ let(:string) { 'bar' }
61
+ let(:addition) { 'foo' }
62
+
63
+ it { should eq 'bar foo' }
64
+ end
65
+
66
+ context 'with the same css class' do
67
+ let(:string) { 'foo bar' }
68
+ let(:addition) { 'foo' }
69
+
70
+ it { should eq 'foo bar' }
71
+ end
72
+
73
+ context 'multiple additions' do
74
+ let(:string) { 'foo bar' }
75
+ let(:addition) { 'foo qux' }
76
+
77
+ it { should eq 'foo bar qux' }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ describe HtmlOptions do
6
+ let(:options) { HtmlOptions.new(attributes) }
7
+ let(:attributes) { {} }
8
+
9
+ it 'takes a hash of attributes' do
10
+ expect(options.to_h).to eq attributes
11
+ end
12
+
13
+ describe '#merge_attrs!' do
14
+ let(:attributes) { { 'id' => 'bar', 'data-alert' => 'true' } }
15
+
16
+ it 'merges attributes with the passed ones' do
17
+ options.merge_attrs!('data-method' => 'post', 'id' => 'foo')
18
+ expect(options.to_h).to eq({
19
+ :'data-alert' => 'true',
20
+ :'data-method' => 'post',
21
+ :'id' => 'foo'
22
+ })
23
+ end
24
+
25
+ context 'css classes' do
26
+ let(:attributes) { { 'class' => 'foo bar' } }
27
+
28
+ it 'merges them appropriately' do
29
+ options.merge_attrs!('class' => 'foo qux')
30
+ expect(options.to_h).to eq(class: 'foo bar qux')
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ module GestartShowcase
4
+ module Helpers
5
+ describe Present do
6
+ let(:object) { Person.new('Steve Ballmer') }
7
+ let(:subclass_object) { EnterpriseCustomer.new('Tito Traves') }
8
+ let(:object_with_no_presenter) { Shop.new }
9
+ let(:context) { Context.new }
10
+
11
+ describe '.present' do
12
+ it 'instantiates a new presenter, inferring the presenter class' do
13
+ PersonPresenter.stub(:new).with(object, context).and_return 'Presenter'
14
+ context.present(object).should == 'Presenter'
15
+ end
16
+ context 'with a superclass presenter' do
17
+ it "instantiates a new presenter, searching presenter class in object ancestors chain" do
18
+ PersonPresenter.stub(:new).with(subclass_object, context).and_return 'Presenter'
19
+ context.present(subclass_object).should == 'Presenter'
20
+ end
21
+ end
22
+ context 'with no existing presenter class' do
23
+ it "raises a PresenterClassNotFound error" do
24
+ expect {
25
+ context.present(object_with_no_presenter)
26
+ }.to raise_error(GestartShowcase::PresenterClassNotFound)
27
+ end
28
+ end
29
+ it 'uses the specified presenter class, when passed' do
30
+ ProjectPresenter.stub(:new).with(object, context).and_return 'Presenter'
31
+ context.present(object, ProjectPresenter).should == 'Presenter'
32
+ end
33
+ it 'uses the specified context, when passed' do
34
+ different_context = double
35
+ context.present(object, ProjectPresenter, different_context).view_context.should == different_context
36
+ end
37
+ end
38
+
39
+ describe '.present_collection' do
40
+ it 'returns a presenter for each object in the collection' do
41
+ collection = [ Person.new('Mark'), Person.new('Luke') ]
42
+
43
+ PersonPresenter.stub(:new).with(collection[0], context).and_return 'foo'
44
+ PersonPresenter.stub(:new).with(collection[1], context).and_return 'bar'
45
+
46
+ presented_collection = context.present_collection(collection)
47
+ presented_collection.should == [ 'foo', 'bar' ]
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe GestartShowcase::Presenter do
4
+ let(:context) { Context.new }
5
+ let(:object) { Project.new('GestartShowcase') }
6
+ let(:subject) { ProjectPresenter.new(object, context) }
7
+
8
+ it 'takes the object and a context as parameters' do
9
+ subject.object.should == object
10
+ subject.view_context.should == context
11
+ end
12
+
13
+ it 'preserves original .class' do
14
+ subject.class.should == Project
15
+ end
16
+
17
+ it 'should be kind of original class' do
18
+ subject.should be_kind_of Project
19
+ end
20
+
21
+ it 'should be instance of original class' do
22
+ subject.should be_instance_of Project
23
+ end
24
+
25
+ it 'delegates methods to object' do
26
+ subject.dummy.should == 'foobar'
27
+ end
28
+
29
+ it 'allows overriding of methods' do
30
+ subject.name.should == 'Presented GestartShowcase'
31
+ end
32
+
33
+ it 'implements :try method the right way' do
34
+ subject.try(:name).should == 'Presented GestartShowcase'
35
+ end
36
+
37
+ it 'allows .h as shortcut to access the context' do
38
+ subject.bold_name.should == '**GestartShowcase**'
39
+ end
40
+
41
+ describe '.present' do
42
+ it 'passes the context' do
43
+ subject.first_collaborator.bold_name.should == '**Ju Liu**'
44
+ end
45
+ end
46
+
47
+ describe '#presents' do
48
+ it 'wraps the specified attributes inside a presenter' do
49
+ subject.owner.sex.should == 'male'
50
+ end
51
+ it 'passes the context' do
52
+ subject.owner.bold_name.should == '**Admin Stefano Verna**'
53
+ end
54
+ it 'returns null if nil_presenter is false or undefined' do
55
+ subject.owner_child.should be_nil
56
+ end
57
+ it 'returns a null presenter if nil_presenter is true' do
58
+ subject.foobar.hi.should eq "hi!"
59
+ end
60
+ end
61
+
62
+ describe '#presents_collection' do
63
+ it 'wraps the specified collection attributes inside a presenter' do
64
+ subject.collaborators.first.sex.should == 'male'
65
+ end
66
+ it 'passes the context' do
67
+ subject.collaborators.first.bold_name.should == '**Ju Liu**'
68
+ end
69
+ end
70
+ end
71
+
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ module GestartShowcase::Traits
4
+ describe Record do
5
+
6
+ let(:view) { RailsViewContext.new }
7
+ let(:record) { Model.new }
8
+ let(:presenter) {
9
+ Class.new(GestartShowcase::Presenter) do
10
+ include Record
11
+
12
+ box do |c|
13
+ c.html_options role: 'actor', class: 'first'
14
+ end
15
+ end
16
+ }
17
+ subject { presenter.new(record, view) }
18
+
19
+ describe '#dom_id' do
20
+ it 'returns a CSS ID for the model' do
21
+ expect(subject.dom_id).to eq 'model_1'
22
+ end
23
+ end
24
+
25
+ describe '#dom_class' do
26
+ it 'returns a CSS class for model' do
27
+ expect(subject.dom_class).to eq 'model'
28
+ end
29
+ end
30
+
31
+ describe '#box' do
32
+ it 'wraps content inside an element that uniquely identifies the record' do
33
+ result = subject.box { "foo" }
34
+ expect(result).to have_tag(:div, with: { class: 'model', id: 'model_1' })
35
+ end
36
+
37
+ it 'adds attributes specified within box block' do
38
+ result = subject.box { "foo" }
39
+ expect(result).to have_tag(:div, with: { role: 'actor', class: 'first' })
40
+ end
41
+
42
+ context 'with a specified tag' do
43
+ it 'uses it' do
44
+ result = subject.box(:span) { "foo" }
45
+ expect(result).to have_tag(:span)
46
+ end
47
+ end
48
+
49
+ context 'with some class' do
50
+ it 'uses it' do
51
+ result = subject.box(class: 'bar') { "foo" }
52
+ expect(result).to have_tag(:div, with: { class: 'model bar'})
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ module GestartShowcase::Helpers
4
+ describe ConfigObject do
5
+
6
+ let(:context_class) {
7
+ Class.new do
8
+ def foo
9
+ 'foo'
10
+ end
11
+
12
+ def bar
13
+ 'bar'
14
+ end
15
+ end
16
+ }
17
+ let(:context) { context_class.new }
18
+
19
+ describe 'to_hash' do
20
+ it 'accepts attr=() notation' do
21
+ result = ConfigObject.new(context) do |c|
22
+ c.first = foo
23
+ c.second = bar
24
+ c.third = '3rd'
25
+ end.to_hash
26
+
27
+ hash = { first: 'foo', second: 'bar', third: '3rd' }
28
+ expect(result).to eq hash
29
+ end
30
+
31
+ it 'accepts attr() notation' do
32
+ result = ConfigObject.new(context) do |c|
33
+ c.first foo
34
+ c.second bar
35
+ c.third '3rd'
36
+ end.to_hash
37
+
38
+ hash = { first: 'foo', second: 'bar', third: '3rd' }
39
+ expect(result).to eq hash
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+
@@ -0,0 +1,12 @@
1
+ require 'rspec-html-matchers'
2
+
3
+ require 'rails'
4
+ require 'gestart_showcase'
5
+ require 'active_model'
6
+ require 'action_view'
7
+
8
+ require_relative './fixtures'
9
+
10
+ RSpec.configure do |config|
11
+ config.include RSpecHtmlMatchers
12
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gestart-showcase
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Gestart Cloud S.r.l.
8
+ - Stefano Verna
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-html-matchers
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ostruct
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Gestart fork of showcase 0.2.5, pruned of what Gestart does not use
84
+ email:
85
+ - dev@gestart.net
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".github/workflows/ci.yml"
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".tool-versions"
94
+ - CHANGELOG.md
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - gestart-showcase.gemspec
100
+ - lib/gestart-showcase.rb
101
+ - lib/gestart_showcase.rb
102
+ - lib/gestart_showcase/errors.rb
103
+ - lib/gestart_showcase/helpers/config_object.rb
104
+ - lib/gestart_showcase/helpers/html_class_attribute.rb
105
+ - lib/gestart_showcase/helpers/html_options.rb
106
+ - lib/gestart_showcase/helpers/module_method_builder.rb
107
+ - lib/gestart_showcase/helpers/present.rb
108
+ - lib/gestart_showcase/presenter.rb
109
+ - lib/gestart_showcase/railtie.rb
110
+ - lib/gestart_showcase/traits.rb
111
+ - lib/gestart_showcase/traits/record.rb
112
+ - lib/gestart_showcase/version.rb
113
+ - spec/fixtures.rb
114
+ - spec/gestart_showcase/helpers/html_class_attribute_spec.rb
115
+ - spec/gestart_showcase/helpers/html_options_spec.rb
116
+ - spec/gestart_showcase/helpers/present_spec.rb
117
+ - spec/gestart_showcase/presenter_spec.rb
118
+ - spec/gestart_showcase/traits/record_spec.rb
119
+ - spec/helpers/config_object_spec.rb
120
+ - spec/spec_helper.rb
121
+ homepage: https://github.com/gestartcloudsrl/gestart-showcase
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '3.1'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.6.9
140
+ specification_version: 4
141
+ summary: 'Gestart fork of showcase: the presenter layer behind every Gestart view'
142
+ test_files:
143
+ - spec/fixtures.rb
144
+ - spec/gestart_showcase/helpers/html_class_attribute_spec.rb
145
+ - spec/gestart_showcase/helpers/html_options_spec.rb
146
+ - spec/gestart_showcase/helpers/present_spec.rb
147
+ - spec/gestart_showcase/presenter_spec.rb
148
+ - spec/gestart_showcase/traits/record_spec.rb
149
+ - spec/helpers/config_object_spec.rb
150
+ - spec/spec_helper.rb