yaks 0.7.6 → 0.7.7
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 +17 -0
- data/Rakefile +58 -38
- data/lib/yaks.rb +3 -4
- data/lib/yaks/attributes.rb +23 -10
- data/lib/yaks/config.rb +4 -0
- data/lib/yaks/configurable.rb +16 -21
- data/lib/yaks/format.rb +1 -1
- data/lib/yaks/format/collection_json.rb +9 -0
- data/lib/yaks/format/halo.rb +16 -13
- data/lib/yaks/html5_forms.rb +22 -1
- data/lib/yaks/mapper.rb +5 -5
- data/lib/yaks/mapper/attribute.rb +1 -1
- data/lib/yaks/mapper/class_methods.rb +3 -1
- data/lib/yaks/mapper/config.rb +9 -16
- data/lib/yaks/mapper/form.rb +41 -0
- data/lib/yaks/mapper/form/field.rb +62 -0
- data/lib/yaks/null_resource.rb +1 -1
- data/lib/yaks/resource.rb +7 -3
- data/lib/yaks/resource/form.rb +31 -0
- data/lib/yaks/runner.rb +11 -11
- data/lib/yaks/stateful_builder.rb +11 -7
- data/lib/yaks/util.rb +3 -2
- data/lib/yaks/version.rb +1 -1
- data/spec/acceptance/models.rb +1 -1
- data/spec/json/confucius.halo.json +2 -4
- data/spec/json/plant_collection.collection.json +4 -0
- data/spec/unit/yaks/attributes_spec.rb +79 -0
- data/spec/unit/yaks/configurable_spec.rb +32 -2
- data/spec/unit/yaks/format/collection_json_spec.rb +59 -27
- data/spec/unit/yaks/format/halo_spec.rb +3 -0
- data/spec/unit/yaks/mapper/{control → form}/field_spec.rb +3 -3
- data/spec/unit/yaks/mapper/{control_spec.rb → form_spec.rb} +12 -12
- data/spec/unit/yaks/mapper_spec.rb +10 -10
- data/spec/unit/yaks/null_resource_spec.rb +50 -7
- data/spec/unit/yaks/resource_spec.rb +4 -4
- data/spec/unit/yaks/runner_spec.rb +38 -2
- data/spec/unit/yaks/util_spec.rb +42 -0
- metadata +20 -58
- data/lib/yaks/mapper/control.rb +0 -82
- data/lib/yaks/resource/control.rb +0 -11
@@ -3,6 +3,7 @@
|
|
3
3
|
module Yaks
|
4
4
|
class Mapper
|
5
5
|
module ClassMethods
|
6
|
+
extend Util::Deprecated
|
6
7
|
include Forwardable,
|
7
8
|
Util,
|
8
9
|
FP
|
@@ -27,7 +28,7 @@ module Yaks
|
|
27
28
|
:profile,
|
28
29
|
:has_one,
|
29
30
|
:has_many,
|
30
|
-
:
|
31
|
+
:form
|
31
32
|
]
|
32
33
|
|
33
34
|
CONFIG_METHODS.each do |method_name|
|
@@ -40,6 +41,7 @@ module Yaks
|
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
44
|
+
deprecated_alias :control, :form
|
43
45
|
end
|
44
46
|
end
|
45
47
|
end
|
data/lib/yaks/mapper/config.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Mapper
|
3
3
|
class Config
|
4
|
+
extend Configurable
|
5
|
+
|
4
6
|
include Attributes.new(
|
5
|
-
type: nil, attributes: [], links: [], associations: [],
|
6
|
-
)
|
7
|
-
Configurable
|
7
|
+
type: nil, attributes: [], links: [], associations: [], forms: []
|
8
|
+
)
|
8
9
|
|
9
10
|
def type(type = Undefined)
|
10
11
|
return @type if type.equal?(Undefined)
|
@@ -16,19 +17,11 @@ module Yaks
|
|
16
17
|
append_to(:attributes, *attrs.map(&Attribute.method(:new)))
|
17
18
|
end
|
18
19
|
|
19
|
-
config_method :link, create: Link,
|
20
|
-
config_method :has_one, create: HasOne,
|
21
|
-
config_method :has_many, create: HasMany,
|
22
|
-
config_method :attribute, create: Attribute,
|
23
|
-
config_method :
|
24
|
-
config_method :control,
|
25
|
-
append_to: :controls,
|
26
|
-
create: StatefulBuilder.new(
|
27
|
-
Control,
|
28
|
-
Control.anima.attribute_names +
|
29
|
-
HTML5Forms::INPUT_TYPES +
|
30
|
-
[:field]
|
31
|
-
)
|
20
|
+
config_method :link, create: Link, append_to: :links
|
21
|
+
config_method :has_one, create: HasOne, append_to: :associations
|
22
|
+
config_method :has_many, create: HasMany, append_to: :associations
|
23
|
+
config_method :attribute, create: Attribute, append_to: :attributes
|
24
|
+
config_method :form, create: Form, append_to: :forms
|
32
25
|
end
|
33
26
|
end
|
34
27
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Form
|
4
|
+
extend Util::Deprecated, Configurable
|
5
|
+
include Attributes.new(
|
6
|
+
name: nil, action: nil, title: nil, method: nil, media_type: nil, fields: []
|
7
|
+
)
|
8
|
+
|
9
|
+
deprecated_alias :href, :action
|
10
|
+
|
11
|
+
Builder = StatefulBuilder.new(
|
12
|
+
self,
|
13
|
+
self.attributes.names + HTML5Forms::INPUT_TYPES + [:field]
|
14
|
+
)
|
15
|
+
|
16
|
+
def self.create(name = nil, options = {}, &block)
|
17
|
+
Builder.build(new({name: name}.merge(options)), &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_to_resource(resource, mapper, _context)
|
21
|
+
resource.add_form(to_resource(mapper))
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_resource(mapper)
|
25
|
+
attrs = {
|
26
|
+
fields: resource_fields(mapper),
|
27
|
+
action: mapper.expand_uri(action, true)
|
28
|
+
}
|
29
|
+
[:name, :title, :method, :media_type].each do |attr|
|
30
|
+
attrs[attr] = mapper.expand_value(public_send(attr))
|
31
|
+
end
|
32
|
+
Resource::Form.new(attrs)
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource_fields(mapper)
|
36
|
+
fields.map { |field| field.to_resource(mapper) }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Mapper
|
3
|
+
class Form
|
4
|
+
class Field
|
5
|
+
extend Configurable
|
6
|
+
include Attributes.new(
|
7
|
+
:name,
|
8
|
+
label: nil,
|
9
|
+
options: []
|
10
|
+
).add(HTML5Forms::FIELD_OPTIONS)
|
11
|
+
|
12
|
+
Builder = StatefulBuilder.new(self, attributes.names)
|
13
|
+
|
14
|
+
def self.create(*args)
|
15
|
+
attrs = args.last.instance_of?(Hash) ? args.pop : {}
|
16
|
+
if name = args.shift
|
17
|
+
attrs = attrs.merge(name: name)
|
18
|
+
end
|
19
|
+
new(attrs)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert to a Resource::Form::Field, expanding any dynamic
|
23
|
+
# values
|
24
|
+
def to_resource(mapper)
|
25
|
+
Resource::Form::Field.new(
|
26
|
+
resource_attributes.each_with_object({}) do |attr, attrs|
|
27
|
+
attrs[attr] = mapper.expand_value(public_send(attr))
|
28
|
+
end.merge(options: options.map(&:to_resource))
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
# All attributes that can be converted 1-to-1 to
|
33
|
+
# Resource::Form::Field
|
34
|
+
def resource_attributes
|
35
|
+
self.class.attributes.names - [:options]
|
36
|
+
end
|
37
|
+
|
38
|
+
# <option>, as used in a <select>
|
39
|
+
class Option
|
40
|
+
include Attributes.new(:value, :label, selected: false)
|
41
|
+
|
42
|
+
def self.create(value, opts = {})
|
43
|
+
new(opts.merge(value: value))
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_resource
|
47
|
+
to_h #placeholder
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
config_method :option, create: Option, append_to: :options
|
52
|
+
end #Field
|
53
|
+
|
54
|
+
config_method :field, create: Field::Builder, append_to: :fields
|
55
|
+
|
56
|
+
HTML5Forms::INPUT_TYPES.each do |type|
|
57
|
+
config_method type, create: Field::Builder, append_to: :fields, defaults: { type: type }
|
58
|
+
end
|
59
|
+
|
60
|
+
end # Form
|
61
|
+
end # Mapper
|
62
|
+
end # Yaks
|
data/lib/yaks/null_resource.rb
CHANGED
@@ -39,7 +39,7 @@ module Yaks
|
|
39
39
|
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
40
40
|
end
|
41
41
|
|
42
|
-
def
|
42
|
+
def add_form(_form)
|
43
43
|
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
44
44
|
end
|
45
45
|
|
data/lib/yaks/resource.rb
CHANGED
@@ -6,7 +6,7 @@ module Yaks
|
|
6
6
|
links: [],
|
7
7
|
attributes: {},
|
8
8
|
subresources: [],
|
9
|
-
|
9
|
+
forms: []
|
10
10
|
)
|
11
11
|
|
12
12
|
def initialize(attrs = {})
|
@@ -18,6 +18,10 @@ module Yaks
|
|
18
18
|
attributes[attr]
|
19
19
|
end
|
20
20
|
|
21
|
+
def find_form(name)
|
22
|
+
forms.find { |form| form.name == name }
|
23
|
+
end
|
24
|
+
|
21
25
|
def seq
|
22
26
|
[self]
|
23
27
|
end
|
@@ -56,8 +60,8 @@ module Yaks
|
|
56
60
|
append_to(:links, link)
|
57
61
|
end
|
58
62
|
|
59
|
-
def
|
60
|
-
append_to(:
|
63
|
+
def add_form(form)
|
64
|
+
append_to(:forms, form)
|
61
65
|
end
|
62
66
|
|
63
67
|
def add_subresource(subresource)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Yaks
|
2
|
+
class Resource
|
3
|
+
class Form
|
4
|
+
include Yaks::Mapper::Form.attributes
|
5
|
+
|
6
|
+
def [](name)
|
7
|
+
fields.find {|field| field.name == name}.value
|
8
|
+
end
|
9
|
+
|
10
|
+
def values
|
11
|
+
fields.each_with_object({}) do |field, values|
|
12
|
+
values[field.name] = field.value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Field
|
17
|
+
include Yaks::Mapper::Form::Field.attributes.add(:error => nil)
|
18
|
+
|
19
|
+
def value(arg = Undefined)
|
20
|
+
return @value if arg.eql?(Undefined)
|
21
|
+
if type == :select
|
22
|
+
selected = options.find { |option| option.selected }
|
23
|
+
selected.value if selected
|
24
|
+
else
|
25
|
+
update(value: arg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/yaks/runner.rb
CHANGED
@@ -8,9 +8,16 @@ module Yaks
|
|
8
8
|
def_delegators :config, :policy, :default_format, :format_options, :primitivize, :serializers
|
9
9
|
|
10
10
|
def call
|
11
|
-
steps
|
11
|
+
process(steps, object)
|
12
|
+
end
|
13
|
+
|
14
|
+
def map(object)
|
15
|
+
process(insert_hooks([[:map, mapper]]), object)
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(operations, input)
|
19
|
+
operations.inject(input) {|memo, (_, step)| step.call(memo, env) }
|
12
20
|
end
|
13
|
-
alias result call
|
14
21
|
|
15
22
|
def context
|
16
23
|
{
|
@@ -38,7 +45,7 @@ module Yaks
|
|
38
45
|
format_class.media_type
|
39
46
|
end
|
40
47
|
|
41
|
-
def
|
48
|
+
def format_name
|
42
49
|
format_class.format_name
|
43
50
|
end
|
44
51
|
|
@@ -74,13 +81,6 @@ module Yaks
|
|
74
81
|
end
|
75
82
|
memoize :primitivizer
|
76
83
|
|
77
|
-
# @param [Hash] opts
|
78
|
-
# @return [String]
|
79
|
-
def format_name
|
80
|
-
options.fetch(:format) { default_format }
|
81
|
-
end
|
82
|
-
memoize :format_name
|
83
|
-
|
84
84
|
def serializer
|
85
85
|
serializers.fetch(format_class.serializer)
|
86
86
|
end
|
@@ -93,7 +93,7 @@ module Yaks
|
|
93
93
|
def insert_hooks(steps)
|
94
94
|
hooks.inject(steps) do |steps, (type, target_step, name, hook)|
|
95
95
|
steps.flat_map do |step_name, callable|
|
96
|
-
if step_name.
|
96
|
+
if step_name.equal? target_step
|
97
97
|
case type
|
98
98
|
when :before
|
99
99
|
[[name, hook], [step_name, callable]]
|
@@ -6,27 +6,31 @@ module Yaks
|
|
6
6
|
# @example
|
7
7
|
#
|
8
8
|
# # This code
|
9
|
-
#
|
9
|
+
# Form.create(:search)
|
10
10
|
# .method("POST")
|
11
11
|
# .action("/search")
|
12
12
|
#
|
13
13
|
# # Can be written as
|
14
|
-
# StatefulBuilder.new(
|
14
|
+
# StatefulBuilder.new(Form, [:method, :action]).create(:search) do
|
15
15
|
# method "POST"
|
16
16
|
# action "/search"
|
17
17
|
# end
|
18
18
|
#
|
19
19
|
class StatefulBuilder < BasicObject
|
20
20
|
def create(*args, &block)
|
21
|
-
@
|
21
|
+
build(@klass.create(*args), &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def build(init_state, &block)
|
25
|
+
@state = init_state
|
22
26
|
instance_eval(&block) if block
|
23
27
|
@state
|
24
28
|
end
|
25
29
|
|
26
|
-
def initialize(klass, methods)
|
30
|
+
def initialize(klass, methods = nil)
|
27
31
|
@klass = klass
|
28
|
-
@methods = methods
|
29
|
-
StatefulMethods.new(methods).send(:extend_object, self)
|
32
|
+
@methods = methods || klass.attributes.names
|
33
|
+
StatefulMethods.new(@methods).send(:extend_object, self)
|
30
34
|
end
|
31
35
|
|
32
36
|
def validate_state(method_name, args)
|
@@ -40,7 +44,7 @@ module Yaks
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def inspect
|
43
|
-
"
|
47
|
+
"#<StatefulBuilder #{@klass} #{@methods.inspect}>"
|
44
48
|
end
|
45
49
|
|
46
50
|
class StatefulMethods < ::Module
|
data/lib/yaks/util.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
|
2
3
|
module Yaks
|
3
4
|
module Util
|
4
5
|
extend self
|
@@ -45,7 +46,7 @@ module Yaks
|
|
45
46
|
context.instance_exec(&maybe_proc)
|
46
47
|
end
|
47
48
|
else
|
48
|
-
maybe_proc.to_proc.()
|
49
|
+
maybe_proc.to_proc.call()
|
49
50
|
end
|
50
51
|
else
|
51
52
|
maybe_proc
|
@@ -56,7 +57,7 @@ module Yaks
|
|
56
57
|
def deprecated_alias(name, actual)
|
57
58
|
define_method name do |*args, &block|
|
58
59
|
$stderr.puts "WARNING: #{self.class}##{name} is deprecated, use `#{actual}'. at #{caller.first}"
|
59
|
-
|
60
|
+
public_send(actual, *args, &block)
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|
data/lib/yaks/version.rb
CHANGED
data/spec/acceptance/models.rb
CHANGED
@@ -28,7 +28,7 @@ class ScholarMapper < LiteratureBaseMapper
|
|
28
28
|
link 'http://literature.example.com/rels/quotes', 'http://literature.example.com/quotes/?author={downcased_pinyin}&q={query}', expand: [:downcased_pinyin], title: 'Search for quotes'
|
29
29
|
link :self, 'http://literature.example.com/authors/{downcased_pinyin}'
|
30
30
|
|
31
|
-
|
31
|
+
form :search do
|
32
32
|
title 'Find a Scholar'
|
33
33
|
method 'POST'
|
34
34
|
media_type 'application/x-www-form-urlencoded'
|
@@ -63,3 +63,82 @@ RSpec.describe Yaks::Attributes do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
+
|
67
|
+
RSpec.describe Yaks::Attributes::InstanceMethods do
|
68
|
+
let(:widget) do
|
69
|
+
Class.new do
|
70
|
+
include Yaks::Attributes.new(:color, :size, options: {})
|
71
|
+
def self.name ; 'Widget' ; end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
let(:widget_container) do
|
76
|
+
Class.new do
|
77
|
+
include Yaks::Attributes.new(widgets: [])
|
78
|
+
def self.name ; 'WidgetContainer' ; end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:fixed_width) do
|
83
|
+
Class.new do
|
84
|
+
def initialize(width)
|
85
|
+
@width = width
|
86
|
+
end
|
87
|
+
|
88
|
+
def inspect
|
89
|
+
"#" * @width
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '#pp' do
|
95
|
+
it 'should render correctly' do
|
96
|
+
expect(widget_container.new(widgets: [
|
97
|
+
widget.new(color: :green, size: 7),
|
98
|
+
widget.new(color: :blue, size: 9, options: {foo: :bar})
|
99
|
+
]).pp).to eql "
|
100
|
+
WidgetContainer.new(
|
101
|
+
widgets: [
|
102
|
+
Widget.new(color: :green, size: 7),
|
103
|
+
Widget.new(color: :blue, size: 9, options: {:foo=>:bar})
|
104
|
+
]
|
105
|
+
)
|
106
|
+
".strip
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should inline short arrays' do
|
110
|
+
expect(widget_container.new(widgets: [
|
111
|
+
fixed_width.new(23),
|
112
|
+
fixed_width.new(22)
|
113
|
+
]).pp).to eql "WidgetContainer.new(widgets: [#######################, ######################])"
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should put longer arrays on multiple lines' do
|
117
|
+
expect(widget_container.new(widgets: [
|
118
|
+
fixed_width.new(23),
|
119
|
+
fixed_width.new(23)
|
120
|
+
]).pp).to eql "WidgetContainer.new(\n widgets: [\n #######################,\n #######################\n ]\n)"
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should puts attributes on multiple lines if total length exceeds 50 chars' do
|
124
|
+
expect(widget.new(color: fixed_width.new(18), size: fixed_width.new(18)).pp).to match /\n/
|
125
|
+
expect(widget.new(color: fixed_width.new(18), size: fixed_width.new(17)).pp).to_not match /\n/
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#append_to' do
|
130
|
+
it 'should append to a named collection' do
|
131
|
+
expect(widget_container.new(widgets: [:bar]).append_to(:widgets, :foo)).to eql widget_container.new(widgets: [:bar, :foo])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '#initialize' do
|
136
|
+
it 'should take hash-based args' do
|
137
|
+
expect(widget_container.new(widgets: [:bar])).to eql widget_container.new.widgets([:bar])
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should use defaults when available' do
|
141
|
+
expect(widget.new(color: :blue, size: 3).options).to eql({})
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|