Skip to content

Commit 6eb97a9

Browse files
author
Nils Henning
committed
add requires and optional keywords for attribute definition mechanism
1 parent 91af713 commit 6eb97a9

File tree

8 files changed

+225
-5
lines changed

8 files changed

+225
-5
lines changed

.byebug_history

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,33 @@
11
continue
2+
PropertyComponent.new(title: 'Test', foo: 'Foo').respond_to?(:title)
3+
PropertyComponent.new.respond_to?(:title)
4+
PropertyComponent.new.respond_to?(:title))
5+
continue
6+
self.class.requires_properties
7+
self.class.optional_properties
8+
continue
9+
self.class.optional_properties
10+
continue
11+
self.class.optional_properties
12+
self.class.requires_properties
13+
prop
14+
self.send(:options)[prop]
15+
self.send(:options)
16+
continue
17+
self
18+
self.class
19+
self.class.respond_to? :required_properties
20+
self.respond_to? :required_properties
21+
self.respond_to? :required_hooks
22+
self.respond_to? :puts
23+
self.responds_to? :puts
24+
self.defined?(:puts)
25+
self.defined(:puts=
26+
self.send(:singleton_method_undefined, :puts)
27+
self.send(:singleton_method_undefined)
28+
self.send(:singleton_method_defined?)
29+
self
30+
continue
231
TestModel.destroy_all
332
TestModel.first
433
load "#{Rails.root}/app/models/test_model.rb"

app/concepts/matestack/ui/core/abbr/abbr.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module Matestack::Ui::Core::Abbr
22
class Abbr < Matestack::Ui::Core::Component::Static
3-
REQUIRED_KEYS = [:title]
3+
requires :title
44

55
def setup
66
@tag_attributes.merge!({

app/concepts/matestack/ui/core/component/base.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Matestack::Ui::Core::Component
22
class Base < Trailblazer::Cell
33
include Matestack::Ui::Core::Cell
44
include Matestack::Ui::Core::HasViewContext
5+
include Matestack::Ui::Core::Properties
56

67
# probably eed to remove for other tests to be green again
78
include Matestack::Ui::Core::DSL
@@ -119,6 +120,7 @@ def self.views_dir
119120
# Special validation logic
120121
def validate_options
121122
if defined? self.class::REQUIRED_KEYS
123+
ActiveSupport::Deprecation.warn("REQUIRED_KEYS is deprecated. Use `require :foo` instead.")
122124
self.class::REQUIRED_KEYS.each do |key|
123125
raise "#{self.class.name}: required key '#{key}' is missing" if options[key].nil?
124126
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
module Matestack::Ui::Core::Properties
2+
3+
class PropertyMissingException < StandardError
4+
end
5+
6+
class PropertyOverwritingExistingMethodException < StandardError
7+
end
8+
9+
# prepend the initializer and add class methods
10+
def self.included(base)
11+
base.class_eval do
12+
extend ClassMethods
13+
end
14+
base.send :prepend, Initializer
15+
end
16+
17+
# initializer calls super and creates instance methods for defined required and optional properties afterwards
18+
module Initializer
19+
def initialize(model=nil, options={})
20+
super
21+
required_hooks
22+
optional_hooks
23+
end
24+
end
25+
26+
module ClassMethods
27+
# define optinoal properties for custom components with `optional :foo, :bar`
28+
def optional(*properties)
29+
properties.each { |property| optional_properties.push(property) }
30+
end
31+
32+
# define required properties for custom components with `requires :title, :foo, :bar`
33+
def requires(*properties)
34+
properties.each { |property| requires_properties.push(property) }
35+
end
36+
37+
# array of properties created from the component
38+
def optional_properties
39+
@component_properties ||= []
40+
end
41+
42+
# array of required properties from the component
43+
def requires_properties
44+
@requires_properties ||= []
45+
end
46+
end
47+
48+
def optional_hooks
49+
self.class.optional_properties.compact.each do |prop|
50+
raise PropertyOverwritingExistingMethodException, "Optional property #{prop} would overwrite already defined instance method for #{self.class}" if self.respond_to? prop
51+
send(:define_singleton_method, prop) do
52+
options[prop]
53+
end
54+
end
55+
end
56+
57+
def required_hooks
58+
self.class.requires_properties.compact.each do |prop|
59+
raise PropertyMissingException, "Required property #{prop} is missing for #{self.class}" if self.send(:options)[prop].nil?
60+
raise PropertyOverwritingExistingMethodException, "Required property #{prop} would overwrite already defined instance method for #{self.class}" if self.respond_to? prop
61+
send(:define_singleton_method, prop) do
62+
options[prop]
63+
end
64+
end
65+
end
66+
67+
end

lib/matestack/ui/core/components.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def self.require_core_component(name)
2020

2121
require_app_path "helpers/matestack/ui/core/application_helper"
2222
require_app_path "lib/matestack/ui/core/has_view_context"
23+
require_app_path "lib/matestack/ui/core/properties"
2324

2425
require_app_path "concepts/matestack/ui/core/component/base"
2526
require_app_path "concepts/matestack/ui/core/component/dynamic"

spec/0.8/components/abbr_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
class ExamplePage < Matestack::Ui::Page
99

1010
def response
11-
abbr title: 'Hypertext Markup Language', text: 'HTML'
11+
abbr title: 'Hypertext Markup Language', text: 'HTML'
1212
end
1313

1414
end
@@ -26,9 +26,9 @@ def response
2626
class ExamplePage < Matestack::Ui::Page
2727

2828
def response
29-
abbr title: 'Cascading Style Sheets' do
30-
span text: 'CSS'
31-
end
29+
abbr title: 'Cascading Style Sheets' do
30+
span text: 'CSS'
31+
end
3232
end
3333

3434
end
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
require_relative '../../../support/utils'
2+
include Utils
3+
4+
describe 'Properties Mechanism', type: :feature, js: true do
5+
6+
before :all do
7+
class PropertyComponent < Matestack::Ui::StaticComponent
8+
requires :title
9+
requires :foo
10+
optional :description, :bar
11+
optional :text
12+
13+
def response
14+
paragraph text: title
15+
paragraph text: foo
16+
paragraph text: description
17+
paragraph text: bar
18+
paragraph text: text
19+
end
20+
21+
register_self_as :property_component
22+
end
23+
end
24+
25+
describe 'missing requires' do
26+
it 'should raise exception if required property is missing' do
27+
class ExamplePage < Matestack::Ui::Page
28+
def response
29+
property_component
30+
end
31+
end
32+
33+
visit '/example'
34+
expect(page).to have_content(Matestack::Ui::Core::Properties::PropertyMissingException.to_s)
35+
expect(page).to have_content('Required property title is missing for PropertyComponent')
36+
end
37+
38+
it 'should raise exception if other required property is missing' do
39+
class ExamplePage < Matestack::Ui::Page
40+
def response
41+
property_component title: 'Title'
42+
end
43+
end
44+
45+
visit '/example'
46+
expect(page).to have_content(Matestack::Ui::Core::Properties::PropertyMissingException.to_s)
47+
expect(page).to have_content('Required property foo is missing for PropertyComponent')
48+
end
49+
end
50+
51+
describe 'defining methods' do
52+
it 'should define instance methods for required properties' do
53+
class ExamplePage < Matestack::Ui::Page
54+
def response
55+
property_component title: 'Title', foo: 'Foo'
56+
end
57+
end
58+
visit '/example'
59+
expect(page).to have_content('Title')
60+
expect(page).to have_content('Foo')
61+
prop_component = PropertyComponent.new(title: 'Title', foo: 'Foo')
62+
expect(prop_component.respond_to?(:title)).to be(true)
63+
expect(prop_component.respond_to?(:foo)).to be(true)
64+
end
65+
66+
it 'should define instance methods for optional properties' do
67+
class ExamplePage < Matestack::Ui::Page
68+
def response
69+
property_component title: 'Title', foo: 'Foo', description: 'Description', bar: 'Bar'
70+
end
71+
end
72+
visit '/example'
73+
expect(page).to have_content('Description')
74+
expect(page).to have_content('Bar')
75+
prop_component = PropertyComponent.new(title: 'Title', foo: 'Foo')
76+
expect(prop_component.respond_to?(:description)).to be(true)
77+
expect(prop_component.respond_to?(:bar)).to be(true)
78+
end
79+
end
80+
81+
it 'should raise exception if required property overwrites existing method' do
82+
class TempPropertyComponent < Matestack::Ui::StaticComponent
83+
requires :response
84+
def response
85+
end
86+
register_self_as :temp_property_component
87+
end
88+
89+
class ExamplePage < Matestack::Ui::Page
90+
def response
91+
temp_property_component response: 'Foobar'
92+
end
93+
end
94+
95+
visit '/example'
96+
expect(page).to have_content(Matestack::Ui::Core::Properties::PropertyOverwritingExistingMethodException.to_s)
97+
expect(page).to have_content('Required property response would overwrite already defined instance method for TempPropertyComponent')
98+
end
99+
100+
it 'should raise exception if optional property overwrites existing method' do
101+
class TempOptionalPropertyComponent < Matestack::Ui::StaticComponent
102+
optional :response
103+
def response
104+
end
105+
register_self_as :temp_optional_property_component
106+
end
107+
108+
class ExamplePage < Matestack::Ui::Page
109+
def response
110+
temp_optional_property_component response: 'Foobar'
111+
end
112+
end
113+
114+
visit '/example'
115+
expect(page).to have_content(Matestack::Ui::Core::Properties::PropertyOverwritingExistingMethodException.to_s)
116+
expect(page).to have_content('Optional property response would overwrite already defined instance method for TempOptionalPropertyComponent')
117+
end
118+
119+
end

spec/support/capybara.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636

3737
Capybara.javascript_driver = :headless_chrome
3838

39+
Capybara.raise_server_errors = true
40+
3941
# Capybara.server = :webrick
4042
#
4143
# Capybara.register_driver :selenium do |app|

0 commit comments

Comments
 (0)