yaks 0.8.3 → 0.9.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 +4 -4
- data/README.md +12 -0
- data/lib/yaks.rb +4 -2
- data/lib/yaks/attributes.rb +1 -1
- data/lib/yaks/builder.rb +10 -10
- data/lib/yaks/collection_mapper.rb +1 -1
- data/lib/yaks/config.rb +1 -1
- data/lib/yaks/configurable.rb +41 -2
- data/lib/yaks/format/json_api.rb +21 -35
- data/lib/yaks/mapper.rb +2 -1
- data/lib/yaks/mapper/form.rb +6 -29
- data/lib/yaks/mapper/form/config.rb +37 -3
- data/lib/yaks/mapper/form/dynamic_field.rb +17 -0
- data/lib/yaks/mapper/form/field.rb +13 -10
- data/lib/yaks/mapper/form/field/option.rb +2 -1
- data/lib/yaks/mapper/form/fieldset.rb +7 -29
- data/lib/yaks/reader/hal.rb +0 -1
- data/lib/yaks/reader/json_api.rb +49 -0
- data/lib/yaks/version.rb +1 -1
- data/spec/acceptance/acceptance_spec.rb +1 -0
- data/spec/json/confucius.json_api.json +19 -20
- data/spec/unit/yaks/attributes_spec.rb +37 -1
- data/spec/unit/yaks/builder_spec.rb +34 -3
- data/spec/unit/yaks/collection_mapper_spec.rb +20 -1
- data/spec/unit/yaks/collection_resource_spec.rb +7 -0
- data/spec/unit/yaks/configurable_spec.rb +84 -0
- data/spec/unit/yaks/format/json_api_spec.rb +91 -2
- data/spec/unit/yaks/mapper/form/field_spec.rb +63 -5
- data/spec/unit/yaks/mapper/form/fieldset_spec.rb +30 -0
- data/spec/unit/yaks/mapper/form_spec.rb +25 -1
- metadata +8 -2
@@ -0,0 +1,17 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Form
|
4
|
+
class DynamicField
|
5
|
+
include Attributes.new(:block)
|
6
|
+
|
7
|
+
def self.create(_opts = nil, &block)
|
8
|
+
new(block: block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_resource_fields(mapper)
|
12
|
+
Config.build_with_object(mapper.object, &block).to_resource_fields(mapper)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -5,13 +5,16 @@ module Yaks
|
|
5
5
|
include Attributes.new(
|
6
6
|
:name,
|
7
7
|
label: nil,
|
8
|
-
options: [].freeze
|
8
|
+
options: [].freeze,
|
9
|
+
if: nil
|
9
10
|
).add(HTML5Forms::FIELD_OPTIONS)
|
10
11
|
|
11
12
|
Builder = Builder.new(self) do
|
12
|
-
def_set :name
|
13
|
-
def_set :label
|
13
|
+
def_set :name, :label
|
14
14
|
def_add :option, create: Option, append_to: :options
|
15
|
+
HTML5Forms::FIELD_OPTIONS.each do |option, _|
|
16
|
+
def_set option
|
17
|
+
end
|
15
18
|
end
|
16
19
|
|
17
20
|
def self.create(*args)
|
@@ -24,18 +27,18 @@ module Yaks
|
|
24
27
|
|
25
28
|
# Convert to a Resource::Form::Field, expanding any dynamic
|
26
29
|
# values
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def to_resource_fields(mapper)
|
31
|
+
return [] if self.if && !mapper.expand_value(self.if)
|
32
|
+
[ Resource::Form::Field.new(
|
33
|
+
(resource_attributes - [:if]).each_with_object({}) do |attr, attrs|
|
34
|
+
attrs[attr] = mapper.expand_value(public_send(attr))
|
35
|
+
end.merge(options: resource_options(mapper))) ]
|
33
36
|
end
|
34
37
|
|
35
38
|
def resource_options(mapper)
|
36
39
|
# make sure all empty options arrays are the same instance,
|
37
40
|
# makes for prettier #pp
|
38
|
-
options.empty? ? options : options.map {|opt| opt.to_resource_field_option(mapper) }
|
41
|
+
options.empty? ? options : options.map {|opt| opt.to_resource_field_option(mapper) }.compact
|
39
42
|
end
|
40
43
|
|
41
44
|
# All attributes that can be converted 1-to-1 to
|
@@ -4,13 +4,14 @@ module Yaks
|
|
4
4
|
class Field
|
5
5
|
# <option>, as used in a <select>
|
6
6
|
class Option
|
7
|
-
include Attributes.new(:value, :label, selected: false, disabled: false)
|
7
|
+
include Attributes.new(:value, :label, selected: false, disabled: false, if: nil)
|
8
8
|
|
9
9
|
def self.create(value, opts = {})
|
10
10
|
new(opts.merge(value: value))
|
11
11
|
end
|
12
12
|
|
13
13
|
def to_resource_field_option(mapper)
|
14
|
+
return if self.if && !mapper.expand_value(self.if)
|
14
15
|
Resource::Form::Field::Option.new(
|
15
16
|
value: mapper.expand_value(value),
|
16
17
|
label: mapper.expand_value(label),
|
@@ -3,39 +3,17 @@ module Yaks
|
|
3
3
|
class Form
|
4
4
|
class Fieldset
|
5
5
|
extend Forwardable
|
6
|
-
include
|
6
|
+
include Attributes.new(:config)
|
7
7
|
|
8
|
-
def_delegators :config, :fields
|
8
|
+
def_delegators :config, :fields
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
def_add :fieldset, create: Fieldset, append_to: :fields
|
13
|
-
HTML5Forms::INPUT_TYPES.each do |type|
|
14
|
-
def_add(type,
|
15
|
-
create: Field::Builder,
|
16
|
-
append_to: :fields,
|
17
|
-
defaults: { type: type }
|
18
|
-
)
|
19
|
-
end
|
20
|
-
def_forward :dynamic
|
10
|
+
def self.create(options = {}, &block)
|
11
|
+
new(config: Config.build(options, &block))
|
21
12
|
end
|
22
13
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def to_resource(mapper)
|
28
|
-
config = dynamic_blocks.inject(self.config) do |config, block|
|
29
|
-
ConfigBuilder.build(config, mapper.object, &block)
|
30
|
-
end
|
31
|
-
|
32
|
-
resource_fields = resource_fields(config.fields, mapper)
|
33
|
-
|
34
|
-
Resource::Form::Fieldset.new(fields: resource_fields)
|
35
|
-
end
|
36
|
-
|
37
|
-
def resource_fields(fields, mapper)
|
38
|
-
fields.map { |field| field.to_resource(mapper) }
|
14
|
+
def to_resource_fields(mapper)
|
15
|
+
return [] if config.if && !mapper.expand_value(config.if)
|
16
|
+
[ Resource::Form::Fieldset.new(fields: config.to_resource_fields(mapper)) ]
|
39
17
|
end
|
40
18
|
end
|
41
19
|
end
|
data/lib/yaks/reader/hal.rb
CHANGED
@@ -7,7 +7,6 @@ module Yaks
|
|
7
7
|
links = convert_links(attributes.delete('_links') || {})
|
8
8
|
embedded = convert_embedded(attributes.delete('_embedded') || {})
|
9
9
|
|
10
|
-
|
11
10
|
Resource.new(
|
12
11
|
type: attributes.delete('type') || type_from_links(links),
|
13
12
|
attributes: Util.symbolize_keys(attributes),
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Yaks
|
2
|
+
module Reader
|
3
|
+
class JsonAPI
|
4
|
+
def call(parsed_json, env = {})
|
5
|
+
attributes = parsed_json['data'].first.dup
|
6
|
+
links = attributes.delete('links') || {}
|
7
|
+
linked = parsed_json['linked'].nil? ? {} : parsed_json['linked'].dup
|
8
|
+
embedded = convert_embedded(links, linked)
|
9
|
+
Resource.new(
|
10
|
+
type: Util.singularize(attributes.delete('type')[/\w+$/]),
|
11
|
+
attributes: Util.symbolize_keys(attributes),
|
12
|
+
subresources: embedded,
|
13
|
+
links: []
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def convert_embedded(links, linked)
|
18
|
+
links.flat_map do |rel, link_data|
|
19
|
+
# If this is a compound link, the link_data will contain either
|
20
|
+
# * 'type' and 'id' for a one to one
|
21
|
+
# * 'type' and 'ids' for a homogeneous to-many relationship
|
22
|
+
# * 'data' being an array where each member has 'type' and 'id' for heterogeneous
|
23
|
+
if !link_data['type'].nil? && !link_data['id'].nil?
|
24
|
+
resource = linked.find{ |item| (item['id'] == link_data['id']) && (item['type'] == link_data['type']) }
|
25
|
+
call({'data' => [resource], 'linked' => linked}).with(rels: [rel])
|
26
|
+
elsif !link_data['type'].nil? && !link_data['ids'].nil?
|
27
|
+
resources = linked.select{ |item| (link_data['ids'].include? item['id']) && (item['type'] == link_data['type']) }
|
28
|
+
members = resources.map { |r|
|
29
|
+
call({'data' => [r], 'linked' => linked})
|
30
|
+
}
|
31
|
+
CollectionResource.new(
|
32
|
+
members: members,
|
33
|
+
type: link_data['type'],
|
34
|
+
rels: [rel]
|
35
|
+
)
|
36
|
+
elsif link_data['data'].present?
|
37
|
+
CollectionResource.new(
|
38
|
+
members: link_data['data'].map { |link|
|
39
|
+
resource = linked.find{ |item| (item['id'] == link['id']) && (item['type'] == link['type']) }
|
40
|
+
call({'data' => [resource], 'linked' => linked})
|
41
|
+
},
|
42
|
+
rels: [rel]
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end.compact
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/yaks/version.rb
CHANGED
@@ -1,51 +1,50 @@
|
|
1
1
|
{
|
2
|
-
"
|
2
|
+
"data": [
|
3
3
|
{
|
4
4
|
"id": 9,
|
5
|
+
"type": "scholars",
|
5
6
|
"href": "http://literature.example.com/authors/kongzi",
|
6
7
|
"name": "孔子",
|
7
8
|
"pinyin": "Kongzi",
|
8
9
|
"latinized": "Confucius",
|
9
10
|
"links": {
|
10
|
-
"works": [11,12]
|
11
|
+
"works": {"type": "works", "ids": [11,12]}
|
11
12
|
}
|
12
13
|
}
|
13
14
|
],
|
14
|
-
"linked":
|
15
|
-
"works": [
|
15
|
+
"linked": [
|
16
16
|
{
|
17
17
|
"id": 11,
|
18
|
+
"type": "works",
|
18
19
|
"href": "http://literature.example.com/work/11",
|
19
20
|
"chinese_name": "論語",
|
20
21
|
"english_name": "Analects",
|
21
22
|
"links": {
|
22
|
-
"quotes": [17, 18],
|
23
|
-
"era": 99
|
23
|
+
"quotes": {"type": "quotes", "ids": [17, 18]},
|
24
|
+
"era": {"type": "erae", "id": 99}
|
24
25
|
}
|
25
26
|
},
|
26
|
-
{
|
27
|
-
"id": 12,
|
28
|
-
"href": "http://literature.example.com/work/12",
|
29
|
-
"chinese_name": "易經",
|
30
|
-
"english_name": "Commentaries to the Yi-jing",
|
31
|
-
"links": {}
|
32
|
-
}
|
33
|
-
],
|
34
|
-
"quotes": [
|
35
27
|
{
|
36
28
|
"id": 17,
|
29
|
+
"type": "quotes",
|
37
30
|
"chinese": "廄焚。子退朝,曰:“傷人乎?” 不問馬。"
|
38
31
|
},
|
39
32
|
{
|
40
33
|
"id": 18,
|
34
|
+
"type": "quotes",
|
41
35
|
"chinese": "子曰:“其恕乎!己所不欲、勿施於人。”"
|
42
|
-
}
|
43
|
-
],
|
44
|
-
"erae": [
|
36
|
+
},
|
45
37
|
{
|
46
38
|
"id": 99,
|
39
|
+
"type": "erae",
|
47
40
|
"name": "Zhou Dynasty"
|
41
|
+
},
|
42
|
+
{
|
43
|
+
"id": 12,
|
44
|
+
"type": "works",
|
45
|
+
"href": "http://literature.example.com/work/12",
|
46
|
+
"chinese_name": "易經",
|
47
|
+
"english_name": "Commentaries to the Yi-jing"
|
48
48
|
}
|
49
|
-
|
50
|
-
}
|
49
|
+
]
|
51
50
|
}
|
@@ -38,7 +38,7 @@ RSpec.describe Yaks::Attributes do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
context 'when extending' do
|
41
|
-
subject { Class.new(super()) { include attributes.add(baz: 7) } }
|
41
|
+
subject { Class.new(super()) { include attributes.add(baz: 7, bar: 4) } }
|
42
42
|
|
43
43
|
it 'should make the new attributes available' do
|
44
44
|
expect(subject.new(foo: 3, baz: 6).baz).to equal 6
|
@@ -48,6 +48,14 @@ RSpec.describe Yaks::Attributes do
|
|
48
48
|
expect(subject.new(foo: 3, baz: 6).foo).to equal 3
|
49
49
|
end
|
50
50
|
|
51
|
+
it 'should take new default values' do
|
52
|
+
expect(subject.new(foo: 3, baz: 6).bar).to equal 4
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should make sure attribute names are uniq' do
|
56
|
+
expect(subject.attributes.names.length).to equal 3
|
57
|
+
end
|
58
|
+
|
51
59
|
context 'without any defaults' do
|
52
60
|
subject { Class.new(super()) { include attributes.add(:bax) } }
|
53
61
|
|
@@ -60,6 +68,34 @@ RSpec.describe Yaks::Attributes do
|
|
60
68
|
end
|
61
69
|
end
|
62
70
|
end
|
71
|
+
|
72
|
+
context 'when removing an attribute with a default' do
|
73
|
+
subject { Class.new(super()) { include attributes.remove(:bar) } }
|
74
|
+
|
75
|
+
it 'should still recognize attributes that were kept' do
|
76
|
+
expect(subject.new(foo: 2).foo).to equal 2
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should no longer recognize the old attributes' do
|
80
|
+
expect { subject.new(foo: 3, bar: 3).bar }.to raise_error
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when removing an attribute without a default' do
|
85
|
+
subject { Class.new(super()) { include attributes.remove(:foo) } }
|
86
|
+
|
87
|
+
it 'should still recognize attributes that were kept' do
|
88
|
+
expect(subject.new(bar: 2).bar).to equal 2
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should no longer recognize the old attributes' do
|
92
|
+
expect { subject.new(foo: 3).foo }.to raise_error
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should keep the defaults' do
|
96
|
+
expect(subject.new.bar).to equal 3
|
97
|
+
end
|
98
|
+
end
|
63
99
|
end
|
64
100
|
|
65
101
|
RSpec.describe Yaks::Attributes::InstanceMethods do
|
@@ -13,13 +13,12 @@ RSpec.describe Yaks::Builder do
|
|
13
13
|
def wrong_type(x, y)
|
14
14
|
"foo #{x} #{y}"
|
15
15
|
end
|
16
|
-
|
17
16
|
end
|
18
17
|
|
19
18
|
subject do
|
20
|
-
Yaks::Builder.new(Buildable) do
|
19
|
+
Yaks::Builder.new(Buildable, [:finalize]) do
|
21
20
|
def_set :foo, :bar
|
22
|
-
def_forward :
|
21
|
+
def_forward :wrong_type, :update
|
23
22
|
end
|
24
23
|
end
|
25
24
|
|
@@ -36,4 +35,36 @@ RSpec.describe Yaks::Builder do
|
|
36
35
|
expect( subject.create(3, 4) { finalize } ).to eql Buildable.new(foo: 7, bar: 8)
|
37
36
|
end
|
38
37
|
|
38
|
+
context 'with no methods to forward' do
|
39
|
+
subject do
|
40
|
+
Yaks::Builder.new(Buildable)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should still work' do
|
44
|
+
expect(subject.create(3,4)).to eql Buildable.new(foo: 3, bar: 4)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#build' do
|
49
|
+
it 'should pass on the initial state if no block is given' do
|
50
|
+
expect(subject.build(:foo)).to equal :foo
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should pass any extra args to the block' do
|
54
|
+
expect(subject.build(Buildable.new(foo: 1, bar: 2), 9) {|f| foo(f)}).to eql(
|
55
|
+
Buildable.new(foo: 9, bar: 2)
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#inspect' do
|
61
|
+
subject do
|
62
|
+
Yaks::Builder.new(Buildable, [:foo, :bar])
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should show the class and methods' do
|
66
|
+
expect(subject.inspect).to eql '#<Builder Buildable [:foo, :bar]>'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
39
70
|
end
|
@@ -3,12 +3,13 @@ RSpec.describe Yaks::CollectionMapper do
|
|
3
3
|
|
4
4
|
subject(:mapper) { mapper_class.new(context) }
|
5
5
|
let(:mapper_class) { described_class }
|
6
|
+
let(:mapper_stack) { [] }
|
6
7
|
|
7
8
|
let(:context) {
|
8
9
|
{ item_mapper: item_mapper,
|
9
10
|
policy: policy,
|
10
11
|
env: {},
|
11
|
-
mapper_stack:
|
12
|
+
mapper_stack: mapper_stack }
|
12
13
|
}
|
13
14
|
|
14
15
|
let(:collection) { [] }
|
@@ -25,6 +26,24 @@ RSpec.describe Yaks::CollectionMapper do
|
|
25
26
|
)
|
26
27
|
end
|
27
28
|
|
29
|
+
it 'should accept a second "env" argument' do
|
30
|
+
expect(mapper.call(collection, {})).to be_a Yaks::CollectionResource
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when at the top of the stack' do
|
34
|
+
it 'should have a "collection" rel derived from the type' do
|
35
|
+
expect(mapper.call(collection).rels).to eql ['rel:the_types']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'when not at the top of the stack' do
|
40
|
+
let(:mapper_stack) { [ :foo ]}
|
41
|
+
|
42
|
+
it 'should not have a rel' do
|
43
|
+
expect(mapper.call(collection).rels).to eql []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
28
47
|
context 'with members' do
|
29
48
|
let(:collection) { [boingboing, wassup]}
|
30
49
|
let(:item_mapper) { PetMapper }
|
@@ -52,6 +52,13 @@ RSpec.describe Yaks::CollectionResource do
|
|
52
52
|
its(:rels) { should eq ['http://api.example.org/rels/orders'] }
|
53
53
|
|
54
54
|
its(:subresources) { should eql [] }
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#seq' do
|
58
|
+
let(:init_opts) { { members: [1,2,3] } }
|
55
59
|
|
60
|
+
it 'iterates over the members' do
|
61
|
+
expect(subject.seq.map(&:next)).to eql [2,3,4]
|
62
|
+
end
|
56
63
|
end
|
57
64
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class Kitten
|
2
|
+
include Yaks::Attributes.new(:furriness)
|
3
|
+
|
4
|
+
def self.create(opts, &block)
|
5
|
+
level = opts[:fur_level]
|
6
|
+
level = block.call(level) if block
|
7
|
+
new(furriness: level)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Hanky
|
12
|
+
include Yaks::Attributes.new(:stickyness, :size, :color)
|
13
|
+
|
14
|
+
def self.create(sticky, opts = {})
|
15
|
+
new(stickyness: sticky, size: opts[:size], color: opts[:color])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
RSpec.describe Yaks::Configurable do
|
20
|
+
let(:suffix) { SecureRandom.hex(16) }
|
21
|
+
subject do
|
22
|
+
eval %Q<
|
23
|
+
class TestConfigurable#{suffix}
|
24
|
+
class Config
|
25
|
+
include Yaks::Attributes.new(color: 'blue', taste: 'sour', contents: [])
|
26
|
+
end
|
27
|
+
extend Yaks::Configurable
|
28
|
+
|
29
|
+
def_add :kitten, create: Kitten, append_to: :contents, defaults: {fur_level: 7}
|
30
|
+
def_add :cat, create: Kitten, append_to: :contents
|
31
|
+
def_add :hanky, create: Hanky, append_to: :contents, defaults: {size: 12, color: :red}
|
32
|
+
end
|
33
|
+
>
|
34
|
+
Kernel.const_get("TestConfigurable#{suffix}")
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.extended' do
|
38
|
+
it 'should initialize an empty config object' do
|
39
|
+
expect(subject.config).to eql subject::Config.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#def_add' do
|
44
|
+
it 'should add' do
|
45
|
+
subject.kitten(fur_level: 9)
|
46
|
+
expect(subject.config.contents).to eql [Kitten.new(furriness: 9)]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should use defaults if configured' do
|
50
|
+
subject.kitten
|
51
|
+
expect(subject.config.contents).to eql [Kitten.new(furriness: 7)]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should work without defaults configured' do
|
55
|
+
subject.cat(fur_level: 3)
|
56
|
+
expect(subject.config.contents).to eql [Kitten.new(furriness: 3)]
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should pass on a block' do
|
60
|
+
subject.cat(fur_level: 3) {|l| l+3}
|
61
|
+
expect(subject.config.contents).to eql [Kitten.new(furriness: 6)]
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should work with a create with positional arguments - defaults' do
|
65
|
+
subject.hanky(3)
|
66
|
+
expect(subject.config.contents).to eql [Hanky.new(stickyness: 3, size: 12, color: :red)]
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should work with a create with positional arguments' do
|
70
|
+
subject.hanky(5, size: 15)
|
71
|
+
expect(subject.config.contents).to eql [Hanky.new(stickyness: 5, size: 15, color: :red)]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
describe '#def_set' do
|
77
|
+
it 'should set' do
|
78
|
+
end
|
79
|
+
end
|
80
|
+
describe '#def_forward' do
|
81
|
+
it 'should forward' do
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|