yaks 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -0
  3. data/Rakefile +58 -38
  4. data/lib/yaks.rb +3 -4
  5. data/lib/yaks/attributes.rb +23 -10
  6. data/lib/yaks/config.rb +4 -0
  7. data/lib/yaks/configurable.rb +16 -21
  8. data/lib/yaks/format.rb +1 -1
  9. data/lib/yaks/format/collection_json.rb +9 -0
  10. data/lib/yaks/format/halo.rb +16 -13
  11. data/lib/yaks/html5_forms.rb +22 -1
  12. data/lib/yaks/mapper.rb +5 -5
  13. data/lib/yaks/mapper/attribute.rb +1 -1
  14. data/lib/yaks/mapper/class_methods.rb +3 -1
  15. data/lib/yaks/mapper/config.rb +9 -16
  16. data/lib/yaks/mapper/form.rb +41 -0
  17. data/lib/yaks/mapper/form/field.rb +62 -0
  18. data/lib/yaks/null_resource.rb +1 -1
  19. data/lib/yaks/resource.rb +7 -3
  20. data/lib/yaks/resource/form.rb +31 -0
  21. data/lib/yaks/runner.rb +11 -11
  22. data/lib/yaks/stateful_builder.rb +11 -7
  23. data/lib/yaks/util.rb +3 -2
  24. data/lib/yaks/version.rb +1 -1
  25. data/spec/acceptance/models.rb +1 -1
  26. data/spec/json/confucius.halo.json +2 -4
  27. data/spec/json/plant_collection.collection.json +4 -0
  28. data/spec/unit/yaks/attributes_spec.rb +79 -0
  29. data/spec/unit/yaks/configurable_spec.rb +32 -2
  30. data/spec/unit/yaks/format/collection_json_spec.rb +59 -27
  31. data/spec/unit/yaks/format/halo_spec.rb +3 -0
  32. data/spec/unit/yaks/mapper/{control → form}/field_spec.rb +3 -3
  33. data/spec/unit/yaks/mapper/{control_spec.rb → form_spec.rb} +12 -12
  34. data/spec/unit/yaks/mapper_spec.rb +10 -10
  35. data/spec/unit/yaks/null_resource_spec.rb +50 -7
  36. data/spec/unit/yaks/resource_spec.rb +4 -4
  37. data/spec/unit/yaks/runner_spec.rb +38 -2
  38. data/spec/unit/yaks/util_spec.rb +42 -0
  39. metadata +20 -58
  40. data/lib/yaks/mapper/control.rb +0 -82
  41. 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
- :control
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
@@ -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: [], controls: []
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, append_to: :links
20
- config_method :has_one, create: HasOne, append_to: :associations
21
- config_method :has_many, create: HasMany, append_to: :associations
22
- config_method :attribute, create: Attribute, append_to: :attributes
23
- config_method :attribute, create: Attribute, append_to: :attributes
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
@@ -39,7 +39,7 @@ module Yaks
39
39
  raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
40
40
  end
41
41
 
42
- def add_control(_control)
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
- controls: []
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 add_control(control)
60
- append_to(:controls, control)
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.inject(object) {|memo, (_, step)| step.call(memo, env) }
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 format
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.eql? target_step
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
- # Control.create(:search)
9
+ # Form.create(:search)
10
10
  # .method("POST")
11
11
  # .action("/search")
12
12
  #
13
13
  # # Can be written as
14
- # StatefulBuilder.new(Control, [:method, :action]).create(:search) do
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
- @state = @klass.create(*args)
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
- "#<#{self.class} #{@klass} #{@methods.inspect}>"
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
- send(actual, *args, &block)
60
+ public_send(actual, *args, &block)
60
61
  end
61
62
  end
62
63
  end
data/lib/yaks/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yaks
2
- VERSION = '0.7.6'
2
+ VERSION = '0.7.7'
3
3
  end
@@ -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
- control :search do
31
+ form :search do
32
32
  title 'Find a Scholar'
33
33
  method 'POST'
34
34
  media_type 'application/x-www-form-urlencoded'
@@ -65,14 +65,12 @@
65
65
  {
66
66
  "name": "name",
67
67
  "label": "Scholar Name",
68
- "type": "text",
69
- "value": null
68
+ "type": "text"
70
69
  },
71
70
  {
72
71
  "name": "pinyin",
73
72
  "label": "Hanyu Pinyin",
74
- "type": "text",
75
- "value": null
73
+ "type": "text"
76
74
  }
77
75
  ]
78
76
  }
@@ -27,6 +27,10 @@
27
27
  { "rel": "profile", "href": "http://api.example.com/doc/plant" }
28
28
  ]
29
29
  }
30
+ ],
31
+ "links": [
32
+ { "href": "http://api.example.com/plants", "rel": "self" },
33
+ { "href": "http://api.example.com/doc/plant_collection", "rel": "profile" }
30
34
  ]
31
35
  }
32
36
  }
@@ -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