-
Notifications
You must be signed in to change notification settings - Fork 29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Component presenter class #26
Closed
Closed
Changes from all commits
Commits
Show all changes
65 commits
Select commit
Hold shift + click to select a range
28386a4
Initial presenter development
tombeynon 8181e18
TODO: Dummy component class
tombeynon b8e8cba
Require component classes in dummy
tombeynon 2e30bc5
Define attributes in class
tombeynon 53fb802
Tests
tombeynon 89eed1e
Readme update
tombeynon 9da1b65
Added defaults example
tombeynon 2ca764e
Moved component lookup to presenter class
tombeynon a7dadc3
Remove rails helpers
tombeynon 861e9d1
Added convenience method for properties
tombeynon 4dd2cb3
Rails autoload and .descendants doesn't work
tombeynon 1a5727a
Attribute defaults should pre-populate hash
tombeynon a0adcec
Deep merge attribute defaults
tombeynon c7021f3
Public methods will be exposed to views
tombeynon 683f9c2
Remove broken #attributes tests (now private)
tombeynon 00f75d6
Handle autoloading in engine initializer
tombeynon c64c8e5
Fix Hound issues
tombeynon e0eab2d
Cleaned up attributes
tombeynon 422a279
Single to double quotes
tombeynon ca3bf20
Redundant self
tombeynon bf12ab0
Line length
tombeynon 728363a
Trailing whitespace, align params..
tombeynon df31920
Use camelize instead of classify
tombeynon 383a056
Rename Attributes to Properties
tombeynon cdf1845
Better `component_for` method
tombeynon 1cf37a4
Presenters now create a view context to render
tombeynon 692f606
Update CardComponent
tombeynon 070e4bf
Tidy presenter and define property methods
tombeynon 17cafd5
Use controller view context over self
tombeynon b4f6748
Fix load issues
tombeynon a15192a
Expose component public methods
tombeynon 9aa58a0
Tidy
tombeynon 6036a8d
Some actual presenter tests
tombeynon 4c24333
Initial presenter development
tombeynon 286d77b
TODO: Dummy component class
tombeynon 9882966
Require component classes in dummy
tombeynon 84d614e
Define attributes in class
tombeynon bdb2c9c
Tests
tombeynon 3dd5821
Readme update
tombeynon b46365a
Added defaults example
tombeynon dabb6c7
Moved component lookup to presenter class
tombeynon 1c59089
Remove rails helpers
tombeynon 9dcaabb
Added convenience method for properties
tombeynon baf1c08
Rails autoload and .descendants doesn't work
tombeynon 897a5c4
Attribute defaults should pre-populate hash
tombeynon 8bc298c
Deep merge attribute defaults
tombeynon 16662fe
Public methods will be exposed to views
tombeynon 13eeeaa
Remove broken #attributes tests (now private)
tombeynon 636b6b2
Handle autoloading in engine initializer
tombeynon 6502d0d
Fix Hound issues
tombeynon 1ade24d
Cleaned up attributes
tombeynon c80984f
Single to double quotes
tombeynon e255d33
Redundant self
tombeynon 682021a
Line length
tombeynon 678f022
Trailing whitespace, align params..
tombeynon 2176069
Use camelize instead of classify
tombeynon 2a458b3
Rename Attributes to Properties
tombeynon 0698f5f
Better `component_for` method
tombeynon 33b208b
Merge pull request #4 from tombeynon/presenter-rendering
tombeynon c6e6ea2
Fix hound issues
tombeynon 2449dcc
Update README with new presenter behaviour
tombeynon a7625d8
Use to_partial_path now we control the view context
tombeynon 166906e
Protect Presenter public methods more reliably
tombeynon c0c9ea5
Attempt to fix Rails 3 error
tombeynon cde8031
Rollback to_partial_path work
tombeynon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,8 @@ | ||
module MountainView | ||
module ComponentHelper | ||
def render_component(slug, properties = {}) | ||
render "#{slug}/#{slug}", properties: properties | ||
component = MountainView::Presenter.component_for(slug, properties) | ||
component.render(controller.view_context) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module MountainView | ||
class Presenter | ||
class_attribute :_properties, instance_accessor: false | ||
self._properties = {} | ||
|
||
attr_reader :slug, :properties | ||
|
||
def initialize(slug, properties = {}) | ||
@slug = slug | ||
@properties = default_properties.deep_merge(properties) | ||
end | ||
|
||
def render(context) | ||
context.extend ViewContext | ||
context.inject_component_context self | ||
context.render partial: partial | ||
end | ||
|
||
def partial | ||
"#{slug}/#{slug}" | ||
end | ||
|
||
private | ||
|
||
def default_properties | ||
self.class._properties.inject({}) do |sum, (k, v)| | ||
sum[k] = v[:default] | ||
sum | ||
end | ||
end | ||
|
||
class << self | ||
def component_for(*args) | ||
klass = "#{args.first.to_s.camelize}Component".safe_constantize | ||
klass ||= self | ||
klass.new(*args) | ||
end | ||
|
||
def properties(*args) | ||
opts = args.extract_options! | ||
properties = args.inject({}) do |sum, name| | ||
sum[name] = opts | ||
sum | ||
end | ||
define_property_methods(args) | ||
self._properties = _properties.merge(properties) | ||
end | ||
alias_method :property, :properties | ||
|
||
private | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing whitespace detected. |
||
|
||
def define_property_methods(names = []) | ||
names.each do |name| | ||
next if method_defined?(name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Trailing whitespace detected. |
||
define_method name do | ||
properties[name.to_sym] | ||
end | ||
end | ||
end | ||
end | ||
|
||
module ViewContext | ||
attr_reader :_component | ||
delegate :properties, to: :_component | ||
|
||
def inject_component_context(component) | ||
@_component = component | ||
protected_methods = MountainView::Presenter.public_methods(false) | ||
methods = component.public_methods(false) - protected_methods | ||
methods.each do |meth| | ||
next if self.class.method_defined?(meth) | ||
self.class.delegate meth, to: :_component | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
class CardComponent < MountainView::Presenter | ||
include ActionView::Helpers::TagHelper | ||
|
||
properties :title, :description, :link, :image_url, :location | ||
property :data, default: [] | ||
|
||
def title | ||
[location, properties[:title]].compact.join(", ") | ||
end | ||
|
||
def has_description? | ||
description.present? | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
require "test_helper" | ||
|
||
class InheritedPresenter < MountainView::Presenter | ||
properties :title, :description | ||
property :data, default: [] | ||
|
||
def title | ||
"Foo#{properties[:title].downcase}" | ||
end | ||
end | ||
|
||
class MountainView::PresenterTest < ActiveSupport::TestCase | ||
test "returns the correct partial path" do | ||
presenter = MountainView::Presenter.new("header") | ||
assert_equal "header/header", presenter.partial | ||
end | ||
|
||
test "exposes properties as provided" do | ||
properties = {foo: "bar", hello: "world"} | ||
presenter = MountainView::Presenter.new("header", properties) | ||
assert_equal properties, presenter.properties | ||
end | ||
|
||
test "inherited presenter returns the correct title" do | ||
presenter = InheritedPresenter.new("inherited", title: "Bar") | ||
assert_equal "Foobar", presenter.title | ||
end | ||
|
||
test "inherited presenter responds to #data" do | ||
presenter = InheritedPresenter.new("inherited", data: ["Foobar"]) | ||
assert_equal ["Foobar"], presenter.data | ||
end | ||
|
||
test "inherited presenter returns the default value for #data" do | ||
presenter = InheritedPresenter.new("inherited", {}) | ||
assert_equal [], presenter.data | ||
end | ||
end |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of naming this method
to_partial_path
? Ref: https://robots.thoughtbot.com/rendering-collections-in-railsIt's a bit more Rails-y, and that way we can use
render component, locals: component.locals
in therender_component
method.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice solution, hadn't even thought of that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, unfortunately Rails does something a bit special with
to_partial_path
. If the controller is namespaced (such asMountainView::StyleguideController
) then it'll namespace theto_partial_path
with the controller namespace, resulting inmountain_view/component/component/
. See the following method..https://github.com/rails/rails/blob/20559834e4959531f02f85cac433127c2cc52b20/actionview/lib/action_view/renderer/partial_renderer.rb#L482
The only way I can see to prevent this behaviour would be to override
partial_path
entirely. Not awful, but maybe overkill for what we're trying to achieve.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, didn't know about that!
Yeah, overriding
partial_path
is definitively overkill. As is,partial
, should be perfectly fine then.Thanks for the research!